Skip to content

Conversation

@rabble
Copy link
Contributor

@rabble rabble commented May 23, 2025

🚀 Relay Crawler Implementation

This PR adds a Cloudflare Worker that automatically crawls Nostr relays every minute and stores events for the notification system.

What's included:

Worker Code

  • worker/cloudflare-worker/src/relay-crawler-worker.ts - The relay crawler worker
  • worker/cloudflare-worker/wrangler-crawler.toml - Cloudflare configuration

GitHub Actions Workflows

  • .github/workflows/setup-relay-crawler.yml - One-time setup to create KV namespaces
  • .github/workflows/deploy-relay-crawler.yml - Auto-deploy on push to main

Features

  • Runs every minute via Cloudflare Cron
  • Crawls multiple relays in parallel
  • Stores events in KV with 7-day TTL
  • Health check endpoint at /health
  • Statistics endpoint at /stats
  • Optional authentication for manual triggers

Setup Required After Merge

  1. Add GitHub Secrets:

    • CF_API_TOKEN
    • CF_ACCOUNT_ID
    • CF_ACCOUNT_SUBDOMAIN
  2. Run the setup workflow to create KV namespaces

  3. Update wrangler-crawler.toml with the KV namespace IDs

  4. Push to trigger deployment

Testing

The worker includes health monitoring and statistics endpoints for verification after deployment.

rabble added 8 commits May 23, 2025 15:40
- Created NotificationPermissionPrompt component that shows after user engagement
- Added useTrackEngagement hook to track when users join groups, create groups, or set up wallet
- Integrated tracking into JoinDialogProvider, CreateGroup, and useCreateCashuWallet
- Updated service worker to handle push notifications and notification clicks
- Added environment variables for worker URL and VAPID public key
- Shows notification prompt 3 seconds after meaningful user action
- Sends push subscription to worker with user's npub
- Generated VAPID key pair for push notifications
- Added VAPID_KEYS.md with instructions for secure key handling
- Created .env.local with public key (added to .gitignore)
- Updated worker package with web-push for key generation
- Clear separation between public key (client-safe) and private key (server-only)
@coderabbitai
Copy link

coderabbitai bot commented May 23, 2025

Walkthrough

This update introduces a comprehensive push notification system for the application, including both frontend and backend changes. It adds a service worker for push notifications, React hooks and UI for managing notification preferences, backend Cloudflare Workers for subscription and notification processing, a notification bot, deployment scripts, extensive documentation, and GitHub Actions workflows for automated deployment and setup. The system supports real-time notifications for group activity, mentions, reactions, and moderation, with user-configurable preferences and secure delivery via VAPID and Web Push.

Changes

Files/Paths Change Summary
.env.example, notification-bot/.env.example, .gitignore Added example environment variable files and updated .gitignore to exclude local env files.
public/sw.js, public/sw.js.bak Added/enhanced service worker scripts for push notification handling and minimal PWA support.
src/components/notifications/NotificationPermissionPrompt.tsx, src/hooks/usePushNotifications.ts, src/hooks/useTrackEngagement.ts, src/pages/settings/EnhancedNotifications.tsx Added React components and hooks for notification permission prompts, push notification management, engagement tracking, and a new notifications/settings page.
src/App.tsx, src/AppRouter.tsx, src/pages/CreateGroup.tsx, src/components/groups/JoinDialogProvider.tsx, src/components/groups/JoinDialogProvider.tsx.bak, src/hooks/useCreateCashuWallet.ts Integrated notification prompt, enhanced notifications page, and engagement tracking; refactored group join dialog provider.
.github/workflows/*, DEPLOYMENT.md, DEPLOYMENT_GUIDE.md, RELEASE_CHECKLIST.md, RELAY_CRAWLER_DEPLOYMENT_CHECKLIST.md, RELAY_CRAWLER_DEPLOYMENT_STATUS.md, .github/workflows/README.md Added and documented GitHub Actions workflows for deployment, setup, validation, and CI/CD.
worker/cloudflare-worker/*, worker/README.md, worker/IMPLEMENTATION.md, worker/VAPID_KEYS.md, worker/worker_prd.md, worker/cloudflare-worker/README-relay-crawler.md, worker/cloudflare-worker/README-github-actions.md, worker/cloudflare-worker/QUICKSTART-github-actions.md, worker/cloudflare-worker/DEPLOYMENT_STATUS.md, worker/cloudflare-worker/NOTIFICATION_PLAN.md, worker/cloudflare-worker/QUICK_DEPLOY.md, worker/cloudflare-worker/RELAY-CRAWLER-SETUP-COMPLETE.md, worker/cloudflare-worker/cloudflare-config.md Added Cloudflare Worker source code for push notification and relay crawler services, comprehensive documentation, configuration, deployment scripts, and quickstart/setup guides.
worker/cloudflare-worker/src/* Implemented notification system, relay crawler, push notification worker, relay monitor, all-in-one worker, enhanced API, test scenarios, and utilities.
worker/deploy/* Added enhanced notification worker, user registration, web push integration, and deployment configuration templates.
notification-bot/*, bot-package.json Implemented notification bot for relay monitoring and notification dispatch, with configuration and build scripts.
push-service/server.js Added Express.js push notification service for Web Push delivery.
check-relay-status.sh, complete-relay-setup.sh, deploy-bot.sh, generate-vapid-keys.sh, manual-kv-setup.sh, setup-cloudflare-secrets.sh, setup-github-actions.sh Added shell scripts for setup, deployment, key generation, and status checking.
NOTIFICATION_ARCHITECTURE.md, NOTIFICATION_IMPLEMENTATION.md, PUSH_NOTIFICATIONS.md, relay-crawler-kv-config.txt Added architecture, implementation, and configuration documentation for the notification and relay crawler systems.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant ServiceWorker
    participant ReactApp
    participant CloudflareWorker
    participant NotificationBot
    participant PushService

    User->>Browser: Visits app, interacts with UI
    Browser->>ReactApp: Loads app
    ReactApp->>User: Shows notification permission prompt
    User->>ReactApp: Enables notifications
    ReactApp->>ServiceWorker: Registers service worker
    ServiceWorker->>Browser: Requests push subscription (VAPID)
    Browser->>ServiceWorker: Returns push subscription
    ServiceWorker->>CloudflareWorker: Registers subscription (via API)
    CloudflareWorker->>KV: Stores subscription and preferences

    NotificationBot->>NostrRelays: Monitors for events (posts, mentions, reactions)
    NostrRelays->>NotificationBot: Sends new events
    NotificationBot->>CloudflareWorker: Dispatches notifications for relevant users
    CloudflareWorker->>PushService: Sends push notification via Web Push
    PushService->>Browser: Delivers push notification
    Browser->>ServiceWorker: Receives push event
    ServiceWorker->>User: Displays notification
    User->>Browser: Clicks notification
    ServiceWorker->>Browser: Opens/focuses app tab
Loading

Possibly related PRs

  • andotherstuff/chorus#263: Implements a comprehensive push notification infrastructure, including service worker support, React hooks, and Cloudflare Worker backend integration, which is directly related to the push notification system in this update.

Poem

In the meadow of code where the relays hum,
A rabbit hops—"Push is here, let’s drum!"
Service workers whisper, React hooks cheer,
Cloudflare clouds gather, notifications appear.
With every hop, a message to send—
Hooray for alerts, from start to end!
🐇✨🔔

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-05-23T21_24_54_582Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 43

🔭 Outside diff range comments (3)
worker/cloudflare-worker/wrangler.toml (1)

19-20: ⚠️ Potential issue

Remove shell command remnants.

Lines 19-20 contain shell command remnants (EOF 2>&1) that don't belong in a TOML configuration file and will cause parsing errors.

 [env.production.vars]
 PUSH_DISPATCH_API = "https://your-push-api.com/dispatch"
-EOF 2>&1
worker/VAPID_KEYS.md (1)

44-46: ⚠️ Potential issue

Remove shell redirection artifact.

The "EOF 2>&1" appears to be a shell redirection artifact that should be removed from the documentation.

-Never log or expose the private key!
-EOF 2>&1
+Never log or expose the private key!
🧰 Tools
🪛 LanguageTool

[style] ~44-~44: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 659 characters long)
Context: ...``` Never log or expose the private key! EOF 2>&1

(EN_EXCESSIVE_EXCLAMATION)

worker/generate-vapid-keys.js (1)

19-20: ⚠️ Potential issue

Fix critical syntax error causing pipeline failure.

The shell redirection artifacts at the end of the file are causing JavaScript parsing errors and pipeline failures.

-console.log('   VAPID_SUBJECT=mailto:[email protected]');
-EOF 2>&1
+console.log('   VAPID_SUBJECT=mailto:[email protected]');

This fix will resolve the parsing errors reported by ESLint and Biome, allowing the script to execute properly.

🧰 Tools
🪛 Biome (1.9.4)

[error] 19-19: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 19-19: Expected an expression but instead found '&'.

Expected an expression here.

(parse)

🪛 GitHub Actions: Test

[error] 19-19: ESLint: Parsing error: Unexpected token 2

♻️ Duplicate comments (1)
.github/workflows/deploy-relay-crawler.yml (1)

176-179: Manual-deploy job repeats the illegal secrets context in environment.url
Refactor similarly or compute the URL in a preceding step and echo it as a job summary instead.

🧰 Tools
🪛 actionlint (1.7.7)

178-178: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

🧹 Nitpick comments (45)
src/hooks/useTrackEngagement.ts (1)

1-29: Consider privacy implications and add error handling.

The hook structure is clean and follows React patterns well. However, consider these improvements:

  1. Privacy concern: Storing engagement data in localStorage keyed by user's public key could expose user behavior patterns to other applications or scripts accessing localStorage.

  2. Error handling: localStorage operations can fail (storage full, private browsing mode, etc.).

Consider this enhanced version with error handling:

 export function useTrackEngagement() {
   const { user } = useCurrentUser();

+  const setEngagementFlag = (key: string) => {
+    if (user) {
+      try {
+        localStorage.setItem(`has-${key}:${user.pubkey}`, 'true');
+      } catch (error) {
+        console.warn('Failed to store engagement tracking:', error);
+      }
+    }
+  };

   const trackJoinedGroup = () => {
-    if (user) {
-      localStorage.setItem(`has-joined-group:${user.pubkey}`, 'true');
-    }
+    setEngagementFlag('joined-group');
   };

   const trackSetWallet = () => {
-    if (user) {
-      localStorage.setItem(`has-set-wallet:${user.pubkey}`, 'true');
-    }
+    setEngagementFlag('set-wallet');
   };

   const trackCreatedGroup = () => {
-    if (user) {
-      localStorage.setItem(`has-created-group:${user.pubkey}`, 'true');
-    }
+    setEngagementFlag('created-group');
   };
worker/cloudflare-worker/wrangler.toml (1)

3-3: Update compatibility date for better feature support.

The compatibility date is set to May 2023, which is quite old. Consider updating to a more recent date for better Cloudflare Workers features and bug fixes.

-compatibility_date = "2023-05-18"
+compatibility_date = "2024-05-01"
worker/cloudflare-worker/test-live.js (1)

1-19: LGTM! Good placeholder for live testing framework.

This script effectively serves as a clear blueprint for the live testing workflow. The console output provides excellent documentation of the intended test flow for Nostr relay operations.

Consider making the script executable and adding error handling for when the actual test implementation is ready.

+// Make the file executable: chmod +x test-live.js
+// TODO: Replace console.log statements with actual test implementation
worker/deploy/wrangler.toml.template (1)

5-5: Security consideration: Account ID placeholder.

The commented account ID serves as a good template. Ensure this is properly set during deployment and never committed to version control when populated.

worker/VAPID_KEYS.md (1)

6-8: Fix markdown code block language specification.

To improve documentation quality and address static analysis warnings, specify languages for code blocks.

-```
+```env
 VITE_VAPID_PUBLIC_KEY=BPgDsbapJe78f06TJ0PFzoLGLsYR6w8Dtl86NcNn0OirUtuxm3i2cluBrI1Xlb5RENvCnLX2S2SA2aGmi9DyhOU
-```
+```

-```
+```bash
    wrangler secret put VAPID_PRIVATE_KEY
-```
+```

-```
+```text
    j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ
-```
+```

-```
+```bash
    wrangler secret put VAPID_SUBJECT
-```
+```

Also applies to: 11-13, 21-23, 26-28, 31-33

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

6-6: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/cloudflare-worker/wrangler-crawler.toml (1)

22-22: Consider the performance impact of running every minute.

The cron schedule * * * * * runs every minute, which could be resource-intensive depending on the number of relays and events processed. Monitor CPU usage and consider adjusting frequency if needed.

Consider starting with a less frequent schedule during initial deployment:

-crons = ["* * * * *"] # Every minute
+crons = ["*/5 * * * *"] # Every 5 minutes
worker/cloudflare-worker/test-notification-system.js (1)

26-38: Enhance test event with more realistic data.

The test event structure is good but could be improved with more realistic Nostr event data to better validate the notification system.

Consider enhancing the test event:

 const testEvent = {
   id: 'test123',
-  kind: 42,
+  kind: 1, // Text note - more common event type
   pubkey: 'testpubkey',
   created_at: Math.floor(Date.now() / 1000),
   tags: [
     ['h', 'protest-net/test-group'],
-    ['p', 'mentioneduser']
+    ['p', 'npub1...'], // Use proper Nostr pubkey format
+    ['e', 'eventid123'] // Add event reference
   ],
   content: 'Hey @mentioneduser check out this urgent message!',
   sig: 'testsig'
 };
src/components/groups/JoinDialogProvider.tsx.bak (1)

1-91: Remove backup file or clarify its purpose.

This appears to be a backup file that might not be needed in the repository. Backup files can clutter the codebase and confuse developers.

Consider removing this backup file if it's no longer needed:

# Remove the backup file
rm src/components/groups/JoinDialogProvider.tsx.bak

If the backup serves a specific purpose, add a comment at the top explaining why it's kept and when it should be removed.

public/sw.js (1)

19-92: Consider adding feature detection for push notifications.

The service worker assumes push notification support without checking if the browser supports the required APIs.

Add feature detection at the top of the file:

+// Check for push notification support
+if (!('PushManager' in window) || !('Notification' in window)) {
+  console.warn('Push notifications not supported in this browser');
+}
+
 // Push notification handling
 self.addEventListener('push', function(event) {
+  if (!self.registration.showNotification) {
+    console.warn('Notifications not supported');
+    return;
+  }
+  
   if (!event.data) return;
worker/cloudflare-worker/DEPLOYMENT_STATUS.md (2)

39-39: Fix bare URL formatting.

The URL should be properly formatted as a markdown link for better readability and compliance with markdown standards.

-- **Endpoint**: https://nostr-nip72-poller.protestnet.workers.dev
+- **Endpoint**: [https://nostr-nip72-poller.protestnet.workers.dev](https://nostr-nip72-poller.protestnet.workers.dev)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

39-39: Bare URL used
null

(MD034, no-bare-urls)


84-84: Remove shell command artifact.

The EOF 2>&1 appears to be a copy-paste artifact from a shell command and should be removed.

-EOF 2>&1
worker/cloudflare-worker/QUICK_DEPLOY.md (3)

7-7: Fix bare URL formatting.

The URL should be properly formatted as a markdown link.

-✅ **Live Worker**: Version 1.0.0 running at https://nostr-nip72-poller.protestnet.workers.dev
+✅ **Live Worker**: Version 1.0.0 running at [https://nostr-nip72-poller.protestnet.workers.dev](https://nostr-nip72-poller.protestnet.workers.dev)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

7-7: Bare URL used
null

(MD034, no-bare-urls)


86-97: Add language specification to code block.

The file structure diagram should specify a language for better rendering.

-```
+```text
 cloudflare-worker/
 ├── src/
 │   ├── worker.ts               # Original worker (v1.0)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

86-86: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


100-100: Remove shell command artifact.

The EOF 2>&1 appears to be a copy-paste artifact and should be removed.

-EOF 2>&1
src/components/notifications/NotificationPermissionPrompt.tsx (1)

47-82: Enhance error handling for service worker and push manager support.

The current implementation could provide more specific error messages for different failure scenarios to improve user experience.

 if (permission === 'granted') {
-  // Check if service worker is supported
-  if ('serviceWorker' in navigator && 'PushManager' in window) {
+  // Check if service worker is supported
+  if (!('serviceWorker' in navigator)) {
+    throw new Error('Service workers are not supported in this browser');
+  }
+  
+  if (!('PushManager' in window)) {
+    throw new Error('Push messaging is not supported in this browser');
+  }
+  
+  try {
     const registration = await navigator.serviceWorker.ready;
     
     // Subscribe to push notifications
     const subscription = await registration.pushManager.subscribe({
       userVisibleOnly: true,
       applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
     });
     
     // Send subscription to worker
     const response = await fetch(`${WORKER_URL}/api/notifications/subscribe`, {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify({
         npub: user.pubkey,
         subscription: subscription.toJSON(),
         timestamp: Date.now()
       })
     });
     
     if (response.ok) {
       localStorage.setItem(`notification-permission:${user.pubkey}`, 'granted');
       toast({
         title: "Notifications enabled",
         description: "You'll now receive notifications for group activity",
       });
       setIsVisible(false);
     } else {
-      throw new Error('Failed to save subscription');
+      const errorText = await response.text();
+      throw new Error(`Failed to save subscription: ${response.status} ${errorText}`);
     }
-  }
+  } catch (subscriptionError) {
+    throw new Error(`Failed to set up push subscription: ${subscriptionError.message}`);
+  }
 }
worker/cloudflare-worker/YOLO_DEPLOYMENT.md (1)

170-177: Consider adjusting the informal tone for broader audience appeal.

While the "YOLO" theme is engaging, the informal language and excessive exclamation marks might not be suitable for all team environments or production documentation.

Consider providing a more formal alternative or noting that this is specifically for development/testing purposes:

-Remember: We're not reckless, we're confidently iterating in production! 🚀
+Remember: This guide supports rapid iteration with proper safeguards in place. 🚀
🧰 Tools
🪛 LanguageTool

[style] ~174-~174: Consider using a more formal and expressive alternative to ‘awesome’.
Context: ... 100ms - 😊 User satisfaction: "This is awesome!" - 🎉 Zero data loss (thanks to event ...

(AWESOME)

worker/cloudflare-worker/README-relay-crawler.md (1)

194-196: Minor grammatical improvement needed.

-1. **WebSocket Connection Failed**
-   - Check if relay URL is correct and accessible
+1. **WebSocket Connection Failed**
+   - Check if the relay URL is correct and accessible
🧰 Tools
🪛 LanguageTool

[uncategorized] ~195-~195: You might be missing the article “the” here.
Context: ...ocket Connection Failed** - Check if relay URL is correct and accessible - Some...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

worker/cloudflare-worker/src/test-scenarios.ts (2)

9-11: Consider making relay URLs configurable.

Hard-coded relay URLs could become stale or unavailable, causing test failures.

-const LIVE_RELAYS = [
-  'wss://relay.chorus.community/'
-];
+const LIVE_RELAYS = process.env.TEST_RELAY_URLS?.split(',') || [
+  'wss://relay.chorus.community/'
+];

172-175: Add error handling for the main execution block.

The current implementation could benefit from more specific error handling when running as a main module.

 // Run the tests
 if (import.meta.main) {
-  runNotificationTests().catch(console.error);
+  runNotificationTests()
+    .then(() => {
+      console.log('✅ All tests completed successfully');
+      process.exit(0);
+    })
+    .catch((error) => {
+      console.error('❌ Test suite failed:', error);
+      process.exit(1);
+    });
 }
worker/cloudflare-worker/test-relay-crawler.js (2)

24-31: Minor: tests.deployed is never read

tests declares a deployed key that is never referenced afterwards. Either remove the dead code or use it in runDeployedTest to pre-populate the prompt default.


34-66: Timeouts & abort signals missing – long-running relays may hang the script

fetch calls lack a timeout or abort controller; if a relay/worker stalls the whole suite will appear frozen. Consider a 5–10 s timeout and surface that as a failure.

-const response = await fetch(`${url}${endpoint}`, { … });
+const controller = new AbortController();
+const timer = setTimeout(() => controller.abort(), 10_000);
+const response = await fetch(`${url}${endpoint}`, {
+  …,
+  signal: controller.signal
+}).finally(() => clearTimeout(timer));
src/components/groups/JoinDialogProvider.tsx (2)

61-65: Memoise contextValue to avoid unnecessary context re-renders

Because openJoinDialog is stable but the object literal is recreated on every render, all consumers re-render every keystroke. Wrap with useMemo.

-const contextValue = { openJoinDialog, closeJoinDialog, isDialogOpen: isOpen };
+const contextValue = React.useMemo(
+  () => ({ openJoinDialog, closeJoinDialog, isDialogOpen: isOpen }),
+  [openJoinDialog, closeJoinDialog, isOpen]
+);

95-109: Button semantics: use type="button" when not inside a <form>

type="submit" inside a standalone dialog has no effect and may surprise assistive tech. Change to button.

-<Button 
-  type="submit" 
+<Button
+  type="button"
worker/deploy/user-registration.js (2)

20-48: Storing all users in a single KV key will not scale and risks race conditions

Every registration performs a read-modify-write on users:all, causing:

  1. 25 MiB KV key size limit – a few thousand users with subscription blobs will exceed it.
  2. Lost updates under concurrent writes – no CAS/etag is used.
  3. High latency for every request.

Consider per-user keys (users:${pubkey}) and list operations via KV-list or Durable Objects to achieve atomicity.


150-167: Inactive-user cleanup uses lastSeen, but registerUser never sets createdAt

The admin users endpoint expects a createdAt field, yet new UserProfile initialisation in registerUser omits it. Add the property to avoid undefined in reports.

.github/workflows/setup-relay-crawler.yml (1)

172-178: Upgrade upload-artifact action to v4

actions/upload-artifact@v3 uses the deprecated Node 16 runner and is flagged by actionlint.
Switch to v4 for security patches and longer support:

- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
🧰 Tools
🪛 actionlint (1.7.7)

173-173: the runner of "actions/upload-artifact@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

worker/cloudflare-worker/relay-crawler-integration-example.md (1)

7-17: Specify a language for fenced diagrams to silence markdownlint

markdownlint reports MD040 because the fenced block after line 7 lacks a language hint.
Simply add text (or console) to improve rendering and tooling support:

```text
┌───────── … 
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

7-7: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/cloudflare-worker/src/relay-crawler-worker.ts (1)

128-132: Timeout race leaves dangling websocket tasks

Promise.race resolves after 50 s but doesn’t abort the per-relay promises; they continue storing to KV even after the scheduled worker is about to be re-invoked.

Pass an AbortSignal or implement your own cancellation so that late results don’t overlap with the next crawl cycle, reducing KV write churn.

worker/IMPLEMENTATION.md (4)

7-11: Add language identifier to fenced code block (markdown-lint MD040)
Markdown-lint flags this block because the triple-back-tick fence omits a language tag.
Even if the diagram is plain text, specify a language such as text to silence the linter and improve syntax-highlighting consistency.

-``` 
+```text
 Nostr Relays → Cloudflare Worker → Push API → Web Push Service → Browsers
                       ↓                ↓
                    KV Storage    PostgreSQL DB

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

7-7: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`74-87`: **Specify `sql` language for the DDL snippet**  
Readers get SQL highlighting and MD040 is satisfied.

```diff
-``` 
+```sql
 CREATE TABLE push_subscriptions (
   ...
 );

---

`99-108`: **Add `dotenv` language to the env-file example**  

```diff
-``` 
+```dotenv
 DATABASE_URL=postgresql://user:pass@localhost:5432/db
 ...

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

99-99: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`110-115`: **Add language tag to Cloudflare Worker example**  

```diff
-``` 
+```dotenv
 RELAY_URL=wss://relay.chorus.community/
 ...

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

111-111: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

</blockquote></details>
<details>
<summary>.github/workflows/deploy-relay-crawler.yml (1)</summary><blockquote>

`96-104`: **Trailing whitespace & missing newline**  
YAML-lint flags these lines; remove trailing spaces and ensure the file ends with a newline to keep the workflow diff-clean.



Also applies to: 149-157, 221-228

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

[error] 96-96: trailing spaces

(trailing-spaces)

---

[error] 99-99: trailing spaces

(trailing-spaces)

---

[error] 104-104: trailing spaces

(trailing-spaces)

</details>

</details>

</blockquote></details>
<details>
<summary>worker/deploy/web-push.js (2)</summary><blockquote>

`138-141`: **Padding logic corrupts valid Base64 strings**  
When `str.length % 4 == 0` the current code always appends four `=` characters.

```diff
-str += new Array(5 - str.length % 4).join('=');
+if (str.length % 4 !== 0) {
+  str += '='.repeat(4 - (str.length % 4));
+}

250-281: Batch loop is sequential – defeats batching goal
for … i += batchSize plus Promise.allSettled is fine, but the await inside each iteration serialises batches.
Consider firing all batches in parallel with concurrency control (e.g., p-limit) or move the wait logic outside the loop to maximise throughput.

worker/cloudflare-worker/src/worker-enhanced.ts (4)

6-8: Unused verifyEvent import – remove to avoid dead code

verifyEvent is imported but never referenced. This will fail tsc --noUnusedLocals and bloats the bundle.

-import { NostrEvent, verifyEvent } from 'nostr-tools';
+import { NostrEvent } from 'nostr-tools';

196-266: Open registration endpoint lacks authentication & rate-limiting

Anyone can spam /register with arbitrary subscriptions, inflating KV usage and allowing malicious endpoints.

Consider:

  • Requiring a signed JWT or HMAC containing the user’s pubkey.
  • Adding a KV counter per IP + CF-Connecting-IP-based rate limit.

376-392: KV.list on large namespaces is slow – add a hard cap or cached metric

KV.list() over user: and group: every /stats request can time out once user count grows (limit 10 k keys per call). Cache the numbers daily in updateStats.


414-427: Health check only tests KV – add relay & push-API probes

A 200 OK while the relay is unreachable gives false assurance. Consider:

  • fetch(env.RELAY_URL, { method: "GET", headers: { accept: "application/nostr+json" }})
  • fetch(env.PUSH_DISPATCH_API + "/health")
worker/cloudflare-worker/NOTIFICATION_PLAN.md (2)

59-60: Add language identifier to fenced diagram to satisfy markdown-lint

MD040 warning can be silenced by specifying a fake language (e.g., text).

-```
+```text

244-250: Possessive apostrophe missing (“users’ timezones”)

Minor grammar tweak improves clarity.

worker/worker_prd.md (3)

40-48: Clarify “upload Push Subscription to backend” – specify application/json & endpoint

Explicit instructions reduce ambiguity for frontend devs implementing U1.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~40-~40: You might be missing the article “the” here.
Context: ...ssions, and upload Push Subscription to backend. 2. Bot trigger — When nostr‑bot ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~47-~47: The preposition ‘to’ may be missing (allow someone to do something).
Context: ... Settings screen lists devices & allows delete. ## 7   Non‑Functional Requirements *...

(ALLOW_TO_DO)


214-214: Comma missing after “Instead”

Applies LanguageTool hint; keeps doc professional.

-Instead it POSTs
+Instead, it POSTs
🧰 Tools
🪛 LanguageTool

[uncategorized] ~214-~214: A comma may be missing after the conjunctive/linking adverb ‘Instead’.
Context: ...id including VAPID keys in edge code). Instead it POSTs to /push/dispatch on the cor...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)


332-337: Three consecutive sentences start with “*” — stylistic lint

Rephrase bullet intros to avoid MD036 repeat-word warning.

worker/deploy/enhanced-worker.js (1)

438-451: Scalability risk: all users loaded into memory every minute

getAllUsers() retrieves the entire user list from a single KV key. With 10-100 k users this will exceed the 128 MB memory limit and 10 MB KV value cap.

Consider:
• Store users under sharded keys (users:<shard>:<pubkey>) and iterate with list().
• Process in batches (e.g. 1000 users per scheduled run) and persist a cursor.
• Move user storage to Durable Objects or D1 where pagination is first-class.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d28736 and dbbc57b.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • worker/cloudflare-worker/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (39)
  • .env.example (1 hunks)
  • .github/workflows/deploy-relay-crawler.yml (1 hunks)
  • .github/workflows/setup-relay-crawler.yml (1 hunks)
  • .gitignore (1 hunks)
  • public/sw.js (1 hunks)
  • public/sw.js.bak (1 hunks)
  • src/App.tsx (2 hunks)
  • src/components/groups/JoinDialogProvider.tsx (3 hunks)
  • src/components/groups/JoinDialogProvider.tsx.bak (1 hunks)
  • src/components/notifications/NotificationPermissionPrompt.tsx (1 hunks)
  • src/hooks/useCreateCashuWallet.ts (3 hunks)
  • src/hooks/useTrackEngagement.ts (1 hunks)
  • src/pages/CreateGroup.tsx (1 hunks)
  • worker/IMPLEMENTATION.md (1 hunks)
  • worker/README.md (1 hunks)
  • worker/VAPID_KEYS.md (1 hunks)
  • worker/cloudflare-worker/DEPLOYMENT_STATUS.md (1 hunks)
  • worker/cloudflare-worker/NOTIFICATION_PLAN.md (1 hunks)
  • worker/cloudflare-worker/QUICK_DEPLOY.md (1 hunks)
  • worker/cloudflare-worker/README-relay-crawler.md (1 hunks)
  • worker/cloudflare-worker/YOLO_DEPLOYMENT.md (1 hunks)
  • worker/cloudflare-worker/package.json (1 hunks)
  • worker/cloudflare-worker/relay-crawler-integration-example.md (1 hunks)
  • worker/cloudflare-worker/src/notification-system.ts (1 hunks)
  • worker/cloudflare-worker/src/relay-crawler-worker.ts (1 hunks)
  • worker/cloudflare-worker/src/test-scenarios.ts (1 hunks)
  • worker/cloudflare-worker/src/test-utils.ts (1 hunks)
  • worker/cloudflare-worker/src/worker-enhanced.ts (1 hunks)
  • worker/cloudflare-worker/test-live.js (1 hunks)
  • worker/cloudflare-worker/test-notification-system.js (1 hunks)
  • worker/cloudflare-worker/test-relay-crawler.js (1 hunks)
  • worker/cloudflare-worker/wrangler-crawler.toml (1 hunks)
  • worker/cloudflare-worker/wrangler.toml (1 hunks)
  • worker/deploy/enhanced-worker.js (1 hunks)
  • worker/deploy/user-registration.js (1 hunks)
  • worker/deploy/web-push.js (1 hunks)
  • worker/deploy/wrangler.toml.template (1 hunks)
  • worker/generate-vapid-keys.js (1 hunks)
  • worker/worker_prd.md (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/hooks/useCreateCashuWallet.ts (1)
src/hooks/useTrackEngagement.ts (1)
  • useTrackEngagement (3-29)
src/App.tsx (1)
src/components/notifications/NotificationPermissionPrompt.tsx (1)
  • NotificationPermissionPrompt (10-152)
src/hooks/useTrackEngagement.ts (1)
src/hooks/useCurrentUser.ts (1)
  • useCurrentUser (9-79)
worker/deploy/user-registration.js (1)
worker/deploy/enhanced-worker.js (2)
  • worker (808-808)
  • worker (870-870)
worker/cloudflare-worker/src/test-utils.ts (1)
src/components/NostrProvider.tsx (1)
  • event (15-24)
worker/cloudflare-worker/src/test-scenarios.ts (1)
worker/cloudflare-worker/src/test-utils.ts (2)
  • generateTestKeypair (100-104)
  • YOLOTester (7-97)
src/components/groups/JoinDialogProvider.tsx (8)
src/hooks/useCurrentUser.ts (1)
  • useCurrentUser (9-79)
src/hooks/useNostrPublish.ts (1)
  • useNostrPublish (59-249)
src/hooks/useTrackEngagement.ts (1)
  • useTrackEngagement (3-29)
src/components/groups/JoinDialogContext.ts (1)
  • JoinDialogContext (10-13)
src/components/ui/dialog.tsx (6)
  • Dialog (110-110)
  • DialogContent (115-115)
  • DialogHeader (116-116)
  • DialogTitle (118-118)
  • DialogDescription (119-119)
  • DialogFooter (117-117)
src/components/ui/label.tsx (1)
  • Label (24-24)
src/components/ui/textarea.tsx (1)
  • Textarea (23-23)
src/components/ui/button.tsx (1)
  • Button (28-28)
worker/cloudflare-worker/src/worker-enhanced.ts (1)
worker/cloudflare-worker/src/notification-system.ts (3)
  • NotificationSystem (54-410)
  • NOTIFICATION_EVENT_KINDS (9-16)
  • UserPreferences (28-52)
🪛 LanguageTool
worker/VAPID_KEYS.md

[style] ~44-~44: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 659 characters long)
Context: ...``` Never log or expose the private key! EOF 2>&1

(EN_EXCESSIVE_EXCLAMATION)

worker/cloudflare-worker/README-relay-crawler.md

[uncategorized] ~195-~195: You might be missing the article “the” here.
Context: ...ocket Connection Failed** - Check if relay URL is correct and accessible - Some...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[typographical] ~239-~239: It seems that a comma is missing.
Context: ...ds on event volume - Typical Usage: Well within free tier limits ## Security -...

(WELL_COMMA)

worker/cloudflare-worker/QUICK_DEPLOY.md

[grammar] ~74-~74: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...s support - Rate limiting 3. YOLO Testing - Test on live relays - Auto-cleanup with k...

(REPEATED_VERBS)

worker/IMPLEMENTATION.md

[style] ~147-~147: Consider using an alternative to strengthen your wording.
Context: ...roups - Join Requests: When someone wants to join user's groups - Reports: Co...

(WANT_KEEN)


[uncategorized] ~217-~217: You might be missing the article “the” here.
Context: ...ations - VAPID keys stored securely in environment - Bot token authentication for internal...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

worker/cloudflare-worker/YOLO_DEPLOYMENT.md

[style] ~174-~174: Consider using a more formal and expressive alternative to ‘awesome’.
Context: ... 100ms - 😊 User satisfaction: "This is awesome!" - 🎉 Zero data loss (thanks to event ...

(AWESOME)


[style] ~187-~187: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 1508 characters long)
Context: ...wrangler rollback` Happy YOLO deploying! 🎊 EOF 2>&1

(EN_EXCESSIVE_EXCLAMATION)

worker/cloudflare-worker/relay-crawler-integration-example.md

[style] ~90-~90: Consider using “who” when you are referring to a person instead of an object.
Context: ...d Worker Here's how to create a worker that uses both crawling and notifications: ...

(THAT_WHO)

worker/cloudflare-worker/NOTIFICATION_PLAN.md

[uncategorized] ~220-~220: It looks like there might be a possessive apostrophe missing. Consider inserting it.
Context: ...ons ### 5.2 Smart Quiet Hours - Detect user timezone from activity patterns - Respe...

(AI_EN_LECTOR_MISSING_NOUN_POSSESSIVE)

worker/worker_prd.md

[uncategorized] ~40-~40: You might be missing the article “the” here.
Context: ...ssions, and upload Push Subscription to backend. 2. Bot trigger — When nostr‑bot ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~47-~47: The preposition ‘to’ may be missing (allow someone to do something).
Context: ... Settings screen lists devices & allows delete. ## 7   Non‑Functional Requirements *...

(ALLOW_TO_DO)


[style] ~143-~143: Try moving the adverb to make the sentence clearer.
Context: ...ing how to debug Service Worker and how to manually send a test push using web-push CLI."* ...

(SPLIT_INFINITIVE)


[style] ~156-~156: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...m cohort for 48 h; monitor metrics. * Phase 2 — 100 % rollout; blog post & in‑app...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[uncategorized] ~176-~176: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...05:42Z(ISO) | No TTL; tracks cursor so polling is idempotent | |...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~214-~214: A comma may be missing after the conjunctive/linking adverb ‘Instead’.
Context: ...id including VAPID keys in edge code). Instead it POSTs to /push/dispatch on the cor...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)


[grammar] ~235-~235: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...KV schema migration doc | *"Create a markdown doc that explains the KV namespaces, ke...

(MARKDOWN_NNP)

🪛 markdownlint-cli2 (0.17.2)
worker/VAPID_KEYS.md

6-6: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


11-11: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


21-21: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


26-26: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


31-31: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/cloudflare-worker/QUICK_DEPLOY.md

7-7: Bare URL used
null

(MD034, no-bare-urls)


86-86: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/IMPLEMENTATION.md

7-7: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


99-99: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


111-111: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/cloudflare-worker/relay-crawler-integration-example.md

7-7: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/cloudflare-worker/DEPLOYMENT_STATUS.md

39-39: Bare URL used
null

(MD034, no-bare-urls)

worker/worker_prd.md

59-59: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


243-243: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)

🪛 Biome (1.9.4)
worker/generate-vapid-keys.js

[error] 19-19: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 19-19: Expected an expression but instead found '&'.

Expected an expression here.

(parse)

worker/deploy/enhanced-worker.js

[error] 821-821: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 831-831: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 840-840: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🪛 GitHub Actions: Test
worker/generate-vapid-keys.js

[error] 19-19: ESLint: Parsing error: Unexpected token 2

🪛 actionlint (1.7.7)
.github/workflows/setup-relay-crawler.yml

125-125: property "create-namespaces" is not defined in object type {}

(expression)


173-173: the runner of "actions/upload-artifact@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

.github/workflows/deploy-relay-crawler.yml

62-62: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)


115-115: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)


178-178: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

🪛 YAMLlint (1.37.1)
.github/workflows/setup-relay-crawler.yml

[error] 40-40: trailing spaces

(trailing-spaces)


[error] 60-60: trailing spaces

(trailing-spaces)


[error] 65-65: trailing spaces

(trailing-spaces)


[error] 67-67: trailing spaces

(trailing-spaces)


[error] 70-70: trailing spaces

(trailing-spaces)


[error] 81-81: trailing spaces

(trailing-spaces)


[error] 84-84: trailing spaces

(trailing-spaces)


[error] 94-94: trailing spaces

(trailing-spaces)


[error] 97-97: trailing spaces

(trailing-spaces)


[error] 106-106: trailing spaces

(trailing-spaces)


[error] 108-108: trailing spaces

(trailing-spaces)


[error] 115-115: trailing spaces

(trailing-spaces)


[error] 118-118: trailing spaces

(trailing-spaces)


[error] 168-168: trailing spaces

(trailing-spaces)


[error] 183-183: trailing spaces

(trailing-spaces)


[error] 187-187: trailing spaces

(trailing-spaces)


[error] 191-191: trailing spaces

(trailing-spaces)


[error] 200-200: no new line character at the end of file

(new-line-at-end-of-file)

.github/workflows/deploy-relay-crawler.yml

[error] 96-96: trailing spaces

(trailing-spaces)


[error] 99-99: trailing spaces

(trailing-spaces)


[error] 104-104: trailing spaces

(trailing-spaces)


[error] 149-149: trailing spaces

(trailing-spaces)


[error] 152-152: trailing spaces

(trailing-spaces)


[error] 157-157: trailing spaces

(trailing-spaces)


[error] 216-216: trailing spaces

(trailing-spaces)


[error] 221-221: trailing spaces

(trailing-spaces)


[error] 224-224: trailing spaces

(trailing-spaces)


[error] 228-228: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (36)
.gitignore (1)

26-26: Good addition for environment file security.

Adding .env.local to gitignore is a best practice that prevents local environment variables from being committed to version control, ensuring security and development consistency.

.env.example (1)

1-4: Well-structured environment variable template.

The environment variables are appropriately named and documented for the push notification system integration.

src/hooks/useCreateCashuWallet.ts (1)

4-4: Excellent integration of engagement tracking.

The engagement tracking is properly implemented:

  • Clean import and usage of the useTrackEngagement hook
  • Called at the appropriate time after successful wallet creation
  • Follows consistent patterns with other engagement tracking in the codebase

This integration supports the notification system by tracking wallet setup as a key user engagement event.

Also applies to: 18-18, 43-44

src/App.tsx (1)

11-11: LGTM! Clean integration of notification component.

The import and placement of NotificationPermissionPrompt in the component tree is well-positioned between UI components and before the main router, ensuring the notification prompt can overlay the entire application when needed.

Also applies to: 42-42

public/sw.js.bak (1)

1-18: Clarify the purpose and improve error handling.

This appears to be a backup service worker, but several concerns arise:

  1. File purpose unclear: The .bak extension suggests this is a backup file, but it's included in the PR. Is this meant to be deployed or just kept as reference?

  2. Missing error handling: Network requests can fail, but there's no fallback handling.

Please clarify:

  • Is this file intended for production use or just as a backup/reference?
  • Should this file be in version control if it's truly a backup?

If this is meant for production, consider adding error handling:

 // Fetch event - pass through all requests to network
 self.addEventListener('fetch', (event) => {
-  // Simply pass through all requests without caching
-  event.respondWith(fetch(event.request));
+  event.respondWith(
+    fetch(event.request).catch((error) => {
+      console.error('Fetch failed:', error);
+      // Return a generic error response or offline page
+      return new Response('Network error', { status: 503 });
+    })
+  );
 });
worker/deploy/wrangler.toml.template (3)

1-4: Well-structured Cloudflare Worker configuration.

The project name, main script reference, and compatibility settings are appropriate for a Nostr polling worker.


7-9: Environment variables correctly configured.

The relay URL is appropriately set as an environment variable, and the BOT_TOKEN comment correctly indicates it should be stored as a secret.


16-17: Appropriate cron schedule for relay polling.

The 30-minute interval strikes a good balance between staying current with relay events and avoiding excessive resource usage.

worker/cloudflare-worker/package.json (5)

1-5: Well-structured package metadata.

The package name, version, and module type configuration are appropriate for a Cloudflare Worker project.


6-19: Comprehensive script configuration.

The script definitions cover all necessary development workflows including testing, deployment, monitoring, and code quality checks. The test scenario targeting is particularly well thought out.


20-22: Appropriate runtime dependency.

The nostr-tools dependency version ^2.3.1 is appropriate for Nostr protocol operations in a Cloudflare Worker environment.


23-33: Comprehensive development tooling.

The development dependencies provide a complete TypeScript development environment with linting, formatting, and Cloudflare Workers support.


34-37: Appropriate Node.js version requirement.

Node.js >= 18 is a good minimum requirement for modern JavaScript features and Cloudflare Workers compatibility.

worker/VAPID_KEYS.md (2)

1-14: Excellent security-conscious documentation.

The clear distinction between public and private key usage, along with specific integration instructions, provides valuable guidance for developers.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

6-6: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


11-11: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


7-7:

❓ Verification inconclusive

Verify if these are example or real VAPID keys.

Please confirm whether these are placeholder example keys or actual keys. If these are real keys intended for production use, they should be regenerated since they're now exposed in the repository.

Also applies to: 27-27


🏁 Script executed:

#!/bin/bash
# Check if these VAPID keys appear elsewhere in the codebase
echo "Searching for VAPID public key usage..."
rg "BPgDsbapJe78f06TJ0PFzoLGLsYR6w8Dtl86NcNn0OirUtuxm3i2cluBrI1Xlb5RENvCnLX2S2SA2aGmi9DyhOU"

echo "Searching for VAPID private key usage..."
rg "j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ"

Length of output: 629


Confirm VAPID Keys Are Placeholders
I only found these values in worker/VAPID_KEYS.md and nowhere in your application code. Please confirm whether these are example placeholders or actual production keys. If they’re real, regenerate and remove them from the repo immediately.

  • File to check:
    • worker/VAPID_KEYS.md (lines 7 and 27)
worker/generate-vapid-keys.js (1)

1-18: Excellent VAPID key generation utility.

The script provides clear, secure key generation with well-structured output and comprehensive setup instructions. The separation between public and private key handling is security-conscious.

worker/cloudflare-worker/wrangler-crawler.toml (1)

13-14:

✅ Verification successful

Replace placeholder KV namespace IDs before deployment.

The configuration contains placeholder values that must be replaced with actual KV namespace IDs. Deploying with these placeholders will cause runtime failures.

Run the following script to verify that the setup workflow creates the required KV namespaces:


🏁 Script executed:

#!/bin/bash
# Description: Check if the setup workflow exists and creates KV namespaces correctly
# Expected: The workflow should contain commands to create KV namespaces

fd "setup-relay-crawler.yml" --type f .github/workflows/ --exec cat {}

Length of output: 8779


Replace placeholder KV namespace IDs before deployment

The CI workflow at .github/workflows/setup-relay-crawler.yml automatically provisions KV namespaces for development, staging, and production and writes their IDs to setup-results.md. To avoid runtime failures:

  • Trigger the workflow with create_namespaces: true.
  • Download the relay-crawler-config artifact or inspect setup-results.md.
  • Copy the generated KV_ID and KV_PREVIEW_ID values into worker/cloudflare-worker/wrangler-crawler.toml (lines 13–14), replacing YOUR_KV_NAMESPACE_ID and YOUR_KV_PREVIEW_ID.

Once updated, you can safely deploy without placeholder errors.

worker/cloudflare-worker/src/test-utils.ts (4)

5-5: LGTM!

The imports from nostr-tools are appropriate and correctly structured for the testing utilities.


7-15: LGTM!

The constructor properly initializes the class with a SimplePool instance and sets up tracking for test events.


77-96: Excellent error handling and cleanup pattern.

The try/catch/finally structure ensures cleanup happens even when tests fail, which is crucial for the YOLO testing approach.


100-104: LGTM!

Clean and straightforward keypair generation utility.

worker/cloudflare-worker/DEPLOYMENT_STATUS.md (1)

1-83: Comprehensive deployment documentation.

The status document provides excellent coverage of completed features, deployment steps, configuration requirements, and performance targets. This will be very helpful for deployment and monitoring.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

39-39: Bare URL used
null

(MD034, no-bare-urls)

worker/cloudflare-worker/QUICK_DEPLOY.md (1)

1-99: Excellent quick deployment guide.

The step-by-step instructions are clear and comprehensive, covering all necessary aspects from KV namespace creation to monitoring. The feature overview and file structure are particularly helpful.

🧰 Tools
🪛 LanguageTool

[grammar] ~74-~74: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...s support - Rate limiting 3. YOLO Testing - Test on live relays - Auto-cleanup with k...

(REPEATED_VERBS)

🪛 markdownlint-cli2 (0.17.2)

7-7: Bare URL used
null

(MD034, no-bare-urls)


86-86: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/README.md (1)

1-12: Excellent documentation structure.

The README provides a comprehensive overview of the notification worker infrastructure with clear sections for deployment status, features, security, and next steps.

Also applies to: 28-61

src/components/notifications/NotificationPermissionPrompt.tsx (1)

155-169: The VAPID key conversion function looks correct.

The urlBase64ToUint8Array function properly handles base64 URL-safe decoding and padding, which is essential for VAPID key conversion.

worker/cloudflare-worker/YOLO_DEPLOYMENT.md (2)

1-42: Comprehensive deployment guide with clear instructions.

The deployment steps are well-structured and include all necessary configuration details. The guide covers dependencies, secrets, configuration, and deployment commands effectively.


139-176: Excellent operational guidance and debugging support.

The performance targets, debugging section, and rollback procedures provide valuable operational context. The emergency stop command is particularly useful.

🧰 Tools
🪛 LanguageTool

[style] ~174-~174: Consider using a more formal and expressive alternative to ‘awesome’.
Context: ... 100ms - 😊 User satisfaction: "This is awesome!" - 🎉 Zero data loss (thanks to event ...

(AWESOME)

worker/cloudflare-worker/README-relay-crawler.md (3)

15-31: Excellent architecture overview and feature documentation.

The documentation clearly explains the crawler's purpose, architecture, and supported event types. The systematic approach to crawling and indexing is well-documented.


221-232: Excellent integration examples for other workers.

The code examples for reading crawled events are practical and demonstrate proper KV storage usage patterns. This will help developers integrate with the crawler effectively.


241-246: Comprehensive security considerations.

The security section properly addresses authentication, event verification, and data handling concerns. This is essential for a production relay crawler.

worker/cloudflare-worker/src/test-scenarios.ts (3)

13-42: Excellent test scenario design for mention notifications.

The test properly simulates a group message with mentions and includes appropriate delays for relay propagation. The event structure follows Nostr standards correctly.


44-85: Comprehensive group activity testing with proper event chaining.

The test creates a realistic sequence of events (discussion → reply → reaction) and demonstrates proper event referencing with reply tags. This effectively tests notification aggregation scenarios.


118-136: Effective keyword alert testing.

The test scenario properly simulates high-priority notifications with urgent keywords and priority tags, which is crucial for emergency notification systems.

worker/deploy/user-registration.js (1)

170-238: Endpoints reuse the request body stream – Cloudflare will throw on second read

Each handler uses await request.json() once, which is correct, but do not call it multiple times on the same Request (e.g. chaining middleware later). Ensure handlers that need both auth and body parse do so in one place or clone the request first.

worker/cloudflare-worker/src/notification-system.ts (1)

355-387: Push delivery lacks retry / error isolation

A transient failure (5xx, network error) inside sendPushNotification throws, bubbling up to flushUserQueue and aborting delivery for every remaining user because queueNotification awaits the promise.

Wrap the call in try/catch and consider exponential back-off or dead-letter KV storage:

try {
  await this.sendPushNotification(prefs, aggregated);
} catch (err) {
  console.error('Push failure for', npub, err);
  // optionally enqueue for retry
}
.github/workflows/setup-relay-crawler.yml (1)

136-148: Undefined outputs → placeholders leak into generated config

The expressions ${{ steps.create-namespaces.outputs.DEVELOPMENT_KV_ID }} etc. will be empty unless you explicitly echo with the correct output syntax and declare outputs for the step.

Inside the bash function you should emit:

echo "${env}_KV_ID=${id}" >> "$GITHUB_OUTPUT"

and make sure the surrounding step has id: create-namespaces (see previous comment).
Otherwise the generated TOML snippet will contain YOUR_DEV_KV_ID, misleading users.

@@ -1,5 +1,6 @@
import { useEffect } from "react";
import { useNavigate, Link } from "react-router-dom";
import { useTrackEngagement } from "@/hooks/useTrackEngagement";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Unused import: useTrackEngagement hook is imported but never used.

The useTrackEngagement hook is imported but not utilized in this component. Based on the PR context introducing engagement tracking for notifications, you should either:

  1. Add engagement tracking when a group is successfully created
  2. Remove the unused import if tracking isn't needed yet

To implement engagement tracking, add the hook usage and tracking call:

export default function CreateGroup() {
  const navigate = useNavigate();
  const { user } = useCurrentUser();
+ const { trackCreatedGroup } = useTrackEngagement();
  const { mutateAsync: publishEvent, isPending: isSubmitting } = useNostrPublish();

Then in the onSubmit function after successful group creation:

      toast({
        title: 'Success',
        description: 'Group created successfully!',
      });
+     // Track that the user has created a group
+     trackCreatedGroup();
      navigate("/groups");

Alternatively, remove the unused import if tracking isn't needed yet:

-import { useTrackEngagement } from "@/hooks/useTrackEngagement";
🤖 Prompt for AI Agents
In src/pages/CreateGroup.tsx at line 3, the useTrackEngagement hook is imported
but not used. To fix this, either remove the import if engagement tracking is
not required yet, or implement the hook by calling useTrackEngagement in the
component and invoke the tracking function inside the onSubmit handler after a
group is successfully created to record the engagement event.

Comment on lines 8 to 9
id = "your-kv-namespace-id"
preview_id = "your-preview-kv-namespace-id"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Replace placeholder values before deployment.

The configuration contains placeholder values that must be updated before deployment:

  • KV namespace IDs (lines 8-9)
  • Production relay URL (line 15)
  • Push dispatch API URL (line 18)

Ensure these values are properly configured in your deployment process. You can verify the setup by checking:

If placeholders are found, they need to be replaced with actual values during deployment.

Also applies to: 15-15, 18-18


🏁 Script executed:

#!/bin/bash
# Verify that placeholder values have been replaced
rg -n "your-.*-id|your-.*\.com" worker/cloudflare-worker/wrangler.toml

Length of output: 208


#!/bin/bash
# Display the first 30 lines of wrangler.toml to inspect the relay URL section
sed -n '1,30p' worker/cloudflare-worker/wrangler.toml

# Search specifically for any ‘relay’ occurrences to locate the production relay URL line
rg -n "relay" worker/cloudflare-worker/wrangler.toml

🏁 Script executed:

#!/bin/bash
# Inspect the beginning of the file for relay URL placeholder
sed -n '1,30p' worker/cloudflare-worker/wrangler.toml

# Search for any “relay” occurrences to pinpoint the production relay URL line
rg -n "relay" worker/cloudflare-worker/wrangler.toml

Length of output: 579


Replace remaining placeholder values in wrangler.toml

The following placeholders must be updated before deployment:

• worker/cloudflare-worker/wrangler.toml
– Lines 8–9: KV namespace IDs (id, preview_id)
– Line 18: PUSH_DISPATCH_API URL

Note: RELAY_URL on line 15 is already set to a real endpoint (wss://relay.chorus.community) and does not need replacement.

You can verify no placeholders remain by running:

rg -n 'your-.*-id|your-.*\.com' worker/cloudflare-worker/wrangler.toml
🤖 Prompt for AI Agents
In worker/cloudflare-worker/wrangler.toml at lines 8-9 and line 18, replace the
placeholder values for KV namespace IDs (id and preview_id) and the
PUSH_DISPATCH_API URL with the actual deployment values. Ensure these are
correctly set before deployment to avoid runtime errors. Verify no placeholders
remain by running the provided ripgrep command to confirm all placeholder
strings have been replaced.

Comment on lines +46 to +47
# Secrets (set via wrangler CLI or dashboard):
# - WORKER_AUTH_TOKEN (optional, for manual trigger authentication) No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the optional authentication token usage.

The comment mentions WORKER_AUTH_TOKEN for manual trigger authentication, but it's unclear how this is used in the worker code or if it's properly secured.

Verify how the authentication token is implemented in the worker:


🏁 Script executed:

#!/bin/bash
# Description: Check how WORKER_AUTH_TOKEN is used in the relay crawler worker
# Expected: Should find proper authentication implementation

rg -A 5 "WORKER_AUTH_TOKEN" worker/cloudflare-worker/src/

Length of output: 1190


Document WORKER_AUTH_TOKEN behavior in wrangler-crawler.toml

The worker enforces the token only when WORKER_AUTH_TOKEN is set in the environment. In that case, it expects incoming requests to include an Authorization: Bearer <WORKER_AUTH_TOKEN> header or it will return 401.

Please update the comments in worker/cloudflare-worker/wrangler-crawler.toml around lines 46–47 to something like:

# Secrets (set via wrangler CLI or dashboard):
# - WORKER_AUTH_TOKEN (optional; if provided, requests must include:
#     Authorization: Bearer <WORKER_AUTH_TOKEN>
#   otherwise the worker will reject with 401 Unauthorized)

Optionally, you can show a curl example:

curl -H "Authorization: Bearer $WORKER_AUTH_TOKEN" https://<your-worker>/...
🤖 Prompt for AI Agents
In worker/cloudflare-worker/wrangler-crawler.toml around lines 46 to 47, update
the comment for WORKER_AUTH_TOKEN to clearly explain that if this token is set
in the environment, the worker requires incoming requests to include an
Authorization header with the Bearer token matching WORKER_AUTH_TOKEN, otherwise
it responds with 401 Unauthorized. Modify the comment to specify this behavior
and optionally add a curl example demonstrating how to include the Authorization
header in requests.


# Resource limits
[limits]
cpu_ms = 50 # 50ms CPU time per request
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

CPU limit may be insufficient for relay crawling.

The 50ms CPU limit per request might be too restrictive for crawling multiple relays in parallel. Relay connections and WebSocket operations could exceed this limit.

Consider increasing the CPU limit for reliable operation:

-cpu_ms = 50 # 50ms CPU time per request
+cpu_ms = 1000 # 1 second CPU time per request
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cpu_ms = 50 # 50ms CPU time per request
- cpu_ms = 50 # 50ms CPU time per request
+ cpu_ms = 1000 # 1 second CPU time per request
🤖 Prompt for AI Agents
In worker/cloudflare-worker/wrangler-crawler.toml at line 44, the CPU time limit
per request is set to 50ms, which may be too low for relay crawling involving
multiple parallel relay connections and WebSocket operations. Increase the
cpu_ms value to a higher number that can accommodate the expected workload to
ensure reliable operation without premature termination.

Comment on lines +90 to +110
const eventCacheKey = `event_cache:${event.id}`;
const cached = await env.KV.get(eventCacheKey);

if (cached) {
console.log(`⏭️ Skipping already processed event: ${event.id}`);
continue;
}

// Process event for notifications
const triggers = await notificationSystem.processEvent(event);

for (const trigger of triggers) {
await notificationSystem.queueNotification(trigger);
notificationCount++;
}

// Cache the event ID to prevent reprocessing
await env.KV.put(eventCacheKey, '1', {
expirationTtl: 86400 // 24 hours
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

KV writes inside tight loop – move to ctx.waitUntil to avoid blocking the scheduler

env.KV.put is awaited for every event, lengthening the cron execution and risking overlap with the next run. Schedule the write asynchronously.

-await env.KV.put(eventCacheKey, '1', { expirationTtl: 86400 });
+ctx.waitUntil(
+  env.KV.put(eventCacheKey, '1', { expirationTtl: 86400 })
+);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In worker/cloudflare-worker/src/worker-enhanced.ts between lines 90 and 110, the
env.KV.put call is awaited inside a loop, causing blocking and potential cron
overlap. To fix this, remove the await from env.KV.put and instead schedule the
put operation using ctx.waitUntil to run asynchronously without blocking the
event loop, ensuring the scheduler is not delayed by KV writes.

Comment on lines +267 to +283
async connect() {
for (const relay of this.relays) {
try {
const ws = new WebSocket(relay);
await new Promise((resolve, reject) => {
ws.onopen = () => {
this.connections.set(relay, ws);
resolve();
};
ws.onerror = () => reject(new Error(`Failed to connect to ${relay}`));
setTimeout(() => reject(new Error(`Timeout connecting to ${relay}`)), 5000);
});
} catch (error) {
console.error(`Failed to connect to relay ${relay}:`, error);
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Outgoing WebSockets are not supported in Cloudflare Workers

new WebSocket(relay) relies on the browser/Web-Socket constructor, but Workers can only create inbound WebSocket pairs (new WebSocketPair()), not arbitrary outbound TCP/WebSocket connections. Deployment will therefore fail at runtime with “WebSocket is not a constructor” (or similar) once executed on Cloudflare.

Use fetch(relay, { method: 'GET', headers: { Upgrade: 'websocket'} }) (cf. connectWebSocket sample in the Workers docs) or the community nostr‐tools RelayPool that wraps this pattern, or move relay polling to a Durable Object / external service that can maintain persistent outbound sockets.

🤖 Prompt for AI Agents
In worker/deploy/enhanced-worker.js around lines 267 to 283, the code
incorrectly uses new WebSocket(relay) to create outbound WebSocket connections,
which is unsupported in Cloudflare Workers and causes runtime errors. Replace
this with the fetch API using fetch(relay, { method: 'GET', headers: { Upgrade:
'websocket' } }) to initiate the WebSocket handshake, then accept the WebSocket
from the response to establish the connection. Alternatively, consider using the
nostr-tools RelayPool or move the relay connection logic to a Durable Object or
external service that supports outbound WebSocket connections.

Comment on lines +296 to +320
const promise = new Promise((resolve) => {
const subscriptionId = Math.random().toString(36).substr(2, 9);
const timeout = setTimeout(() => {
ws.send(JSON.stringify(['CLOSE', subscriptionId]));
resolve([]);
}, options.timeout || 10000);

const handler = (event) => {
try {
const message = JSON.parse(event.data);
if (message[0] === 'EVENT' && message[1] === subscriptionId) {
events.push(message[2]);
} else if (message[0] === 'EOSE' && message[1] === subscriptionId) {
clearTimeout(timeout);
ws.removeEventListener('message', handler);
ws.send(JSON.stringify(['CLOSE', subscriptionId]));
resolve(events);
}
} catch (e) {
console.error('Error parsing relay message:', e);
}
};

ws.addEventListener('message', handler);
ws.send(JSON.stringify(['REQ', subscriptionId, ...filters]));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential event-listener leak on query timeout

When the timeout fires you close the subscription but never detach the handler, leaving a dangling listener that accumulates on every poll:

const timeout = setTimeout(() => {
  ws.send(JSON.stringify(['CLOSE', subscriptionId]));
  resolve([]);
}, );

Add ws.removeEventListener('message', handler); clearTimeout(timeout); inside the timeout callback to avoid memory & CPU leakage over days of uptime.

- const timeout = setTimeout(() => {
-   ws.send(JSON.stringify(['CLOSE', subscriptionId]));
-   resolve([]);
- }, options.timeout || 10000);
+ const timeout = setTimeout(() => {
+   ws.removeEventListener('message', handler);
+   ws.send(JSON.stringify(['CLOSE', subscriptionId]));
+   clearTimeout(timeout);
+   resolve([]);
+ }, options.timeout || 10000);
🤖 Prompt for AI Agents
In worker/deploy/enhanced-worker.js between lines 296 and 320, the timeout
callback closes the subscription but does not remove the 'message' event
listener, causing a potential memory leak. To fix this, inside the timeout
function, add ws.removeEventListener('message', handler) to detach the event
listener and also clearTimeout(timeout) to properly clean up the timer.

Comment on lines +826 to +844
case '/register':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}

const registrationData = await request.json();
await worker.registerUser(registrationData);
return new Response(JSON.stringify({ success: true }));

case '/unregister':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}

const unregData = await request.json();
await worker.unregisterUser(unregData.pubkey);
return new Response(JSON.stringify({ success: true }));

case '/test':
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unauthenticated registration endpoints allow arbitrary account takeover

/register and /unregister accept any JSON payload without validating signatures or bearer tokens. A malicious caller can overwrite or delete another user’s subscription by submitting their pubkey.

Minimum mitigation:

  1. Require an HMAC/VAPID token or Nostr-signed payload proving ownership of the pubkey.
  2. Rate-limit the endpoints.
  3. Validate the structure & size of subscription objects to prevent KV bloat.
🧰 Tools
🪛 Biome (1.9.4)

[error] 831-831: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 840-840: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In worker/deploy/enhanced-worker.js around lines 826 to 844, the /register and
/unregister endpoints accept JSON payloads without verifying ownership, allowing
unauthorized account takeover. To fix this, implement verification by requiring
an HMAC or VAPID token or a Nostr-signed payload that proves the caller owns the
pubkey. Additionally, add rate limiting to these endpoints to prevent abuse and
validate the structure and size of subscription objects to avoid excessive KV
storage usage.

Comment on lines +810 to +857
try {
switch (url.pathname) {
case '/health':
return new Response(JSON.stringify({
status: 'ok',
timestamp: new Date().toISOString()
}), {
headers: { 'Content-Type': 'application/json' }
});

case '/stats':
const stats = await worker.getStats();
return new Response(JSON.stringify(stats), {
headers: { 'Content-Type': 'application/json' }
});

case '/register':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}

const registrationData = await request.json();
await worker.registerUser(registrationData);
return new Response(JSON.stringify({ success: true }));

case '/unregister':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}

const unregData = await request.json();
await worker.unregisterUser(unregData.pubkey);
return new Response(JSON.stringify({ success: true }));

case '/test':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}

await worker.processNotifications();
return new Response(JSON.stringify({
message: 'Test run completed',
timestamp: new Date().toISOString()
}));

default:
return new Response('Not found', { status: 404 });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Lexical declarations inside switch violate JS spec & fail Biome rules

const registrationData, unregData, and others are declared directly in case clauses.
In JavaScript this is undefined-behaviour because declarations are hoisted across clauses. Static analysis flags it (noSwitchDeclarations).

Wrap each case body in a block. Example for /register (repeat for the other cases):

-        case '/register':
-          if (request.method !== 'POST') {
-            return new Response('Method not allowed', { status: 405 });
-          }
-          
-          const registrationData = await request.json();
-          await worker.registerUser(registrationData);
-          return new Response(JSON.stringify({ success: true }));
+        case '/register': {
+          if (request.method !== 'POST') {
+            return new Response('Method not allowed', { status: 405 });
+          }
+
+          const registrationData = await request.json();
+          await worker.registerUser(registrationData);
+          return new Response(JSON.stringify({ success: true }));
+        }

Apply the same curly-brace scoping for /unregister and /test.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
switch (url.pathname) {
case '/health':
return new Response(JSON.stringify({
status: 'ok',
timestamp: new Date().toISOString()
}), {
headers: { 'Content-Type': 'application/json' }
});
case '/stats':
const stats = await worker.getStats();
return new Response(JSON.stringify(stats), {
headers: { 'Content-Type': 'application/json' }
});
case '/register':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const registrationData = await request.json();
await worker.registerUser(registrationData);
return new Response(JSON.stringify({ success: true }));
case '/unregister':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const unregData = await request.json();
await worker.unregisterUser(unregData.pubkey);
return new Response(JSON.stringify({ success: true }));
case '/test':
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
await worker.processNotifications();
return new Response(JSON.stringify({
message: 'Test run completed',
timestamp: new Date().toISOString()
}));
default:
return new Response('Not found', { status: 404 });
}
try {
switch (url.pathname) {
case '/health':
return new Response(JSON.stringify({
status: 'ok',
timestamp: new Date().toISOString()
}), {
headers: { 'Content-Type': 'application/json' }
});
case '/stats':
const stats = await worker.getStats();
return new Response(JSON.stringify(stats), {
headers: { 'Content-Type': 'application/json' }
});
case '/register': {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const registrationData = await request.json();
await worker.registerUser(registrationData);
return new Response(JSON.stringify({ success: true }));
}
case '/unregister': {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const unregData = await request.json();
await worker.unregisterUser(unregData.pubkey);
return new Response(JSON.stringify({ success: true }));
}
case '/test': {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
await worker.processNotifications();
return new Response(JSON.stringify({
message: 'Test run completed',
timestamp: new Date().toISOString()
}));
}
default:
return new Response('Not found', { status: 404 });
}
} catch (err) {
// ...
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 821-821: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 831-831: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 840-840: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In worker/deploy/enhanced-worker.js around lines 810 to 857, the const
declarations inside switch cases violate JavaScript spec and cause static
analysis errors. To fix this, wrap the code inside each case ('/register',
'/unregister', '/test') in curly braces to create a block scope, ensuring the
const declarations are properly scoped and do not leak across cases.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 48

♻️ Duplicate comments (5)
worker/cloudflare-worker/wrangler.toml (1)

10-11: ⚠️ Potential issue

KV namespace placeholders must be replaced before deployment.

The pipeline failure indicates that KV namespace IDs are still using placeholder values. These must be updated with actual namespace IDs from Cloudflare.

As indicated in the pipeline failure, these placeholder values will cause deployment failures. Run the setup workflow or manually create KV namespaces and update these values:

# Create KV namespaces and get IDs
wrangler kv:namespace create "chorus-notifications-kv"
wrangler kv:namespace create "chorus-notifications-kv" --preview

Then update the configuration with the returned IDs.

worker/cloudflare-worker/src/worker-enhanced.ts (4)

72-73: Guard against NaN (duplicate of earlier feedback)
Parsing lastSeen without validation can yield NaN, causing the poller to request events “since 1970”.
Refer to previous suggestion with Number.isFinite.


148-151: Outbound WebSockets unsupported in Workers (duplicate of earlier feedback)
new WebSocket(relayUrl) will throw at runtime.
See previous discussion; switch to an HTTP relay proxy or Durable Object.


355-369: stats typed as unknown (duplicate of earlier feedback)
Using unknown and then accessing properties defeats type safety.
Define a proper interface as previously recommended.


282-283: ⚠️ Potential issue

UserPreferences type missing – compile error persists
handleUserUnregistration() references UserPreferences but the interface is not imported in this file.

-import { NotificationSystem, NOTIFICATION_EVENT_KINDS } from './notification-system';
+import { NotificationSystem, NOTIFICATION_EVENT_KINDS, UserPreferences } from './notification-system';

Add the import or move the interface locally.

🧹 Nitpick comments (60)
relay-crawler-kv-config.txt (1)

1-24: Well-structured KV configuration template.

The configuration properly defines KV namespace bindings for all environments with clear organization and helpful comments.

Consider adding a note at the top indicating this is a template:

 # Relay Crawler KV Configuration
+# TEMPLATE FILE - Replace placeholder values with actual KV namespace IDs
 # Generated: Fri May 23 23:18:27 CEST 2025
🧰 Tools
🪛 LanguageTool

[uncategorized] ~23-~23: A period might be missing here.
Context: ..._SUBDOMAIN.workers.dev # - Development: https://relay-crawler.YOUR_SUBDOMAIN.workers.dev

(AI_EN_LECTOR_MISSING_PUNCTUATION_PERIOD)

worker/VAPID_KEYS.md (1)

6-8: Consider using placeholder for public key as well.

While public keys are safe to expose, using a placeholder in documentation prevents confusion about which key to use.

-BPgDsbapJe78f06TJ0PFzoLGLsYR6w8Dtl86NcNn0OirUtuxm3i2cluBrI1Xlb5RENvCnLX2S2SA2aGmi9DyhOU
+YOUR_VAPID_PUBLIC_KEY_HERE

worker/cloudflare-worker/cloudflare-config.md (4)

4-7: Consider security implications of exposing account details.

The documentation exposes sensitive account information including Account ID and email address. Consider whether this information should be documented in a public repository.

If this is intended for public access, ensure these details are appropriate to share. If not, consider moving sensitive information to a separate private configuration file or using placeholder values.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

7-7: Bare URL used
null

(MD034, no-bare-urls)


11-11: Use proper markdown link formatting.

The bare URL should be formatted as a proper markdown link for better readability and compliance with markdown standards.

-Go to: https://github.com/[your-username]/groups72/settings/secrets/actions
+Go to: [GitHub Repository Secrets](https://github.com/[your-username]/groups72/settings/secrets/actions)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

11-11: Bare URL used
null

(MD034, no-bare-urls)


15-19: Specify language for code block.

The configuration code block should specify the language for proper syntax highlighting.

-```
+```bash
 CF_ACCOUNT_SUBDOMAIN: protestnet
 CF_ACCOUNT_ID: c84e7a9bf7ed99cb41b8e73566568c75
 CF_API_TOKEN: [YOUR_API_TOKEN]

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

15-15: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

</details>

</details>

---

`25-27`: **Use proper markdown link formatting for URLs.**

The bare URLs should be formatted as proper markdown links for better accessibility and compliance with markdown standards.


```diff
-- **Production**: https://relay-crawler-prod.protestnet.workers.dev
-- **Staging**: https://relay-crawler-staging.protestnet.workers.dev
-- **Development**: https://relay-crawler.protestnet.workers.dev
+- **Production**: [relay-crawler-prod.protestnet.workers.dev](https://relay-crawler-prod.protestnet.workers.dev)
+- **Staging**: [relay-crawler-staging.protestnet.workers.dev](https://relay-crawler-staging.protestnet.workers.dev)
+- **Development**: [relay-crawler.protestnet.workers.dev](https://relay-crawler.protestnet.workers.dev)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

25-25: Bare URL used
null

(MD034, no-bare-urls)


26-26: Bare URL used
null

(MD034, no-bare-urls)


27-27: Bare URL used
null

(MD034, no-bare-urls)

deploy-bot.sh (1)

69-69: Security consideration: Validate relay URLs.

The default relay URLs should be validated to ensure they're trustworthy endpoints to prevent potential security issues.

Consider adding a comment or validation for the default relay URLs to ensure they're from trusted sources:

 # Relays to connect to (comma-separated)
+# Note: Only use trusted relay endpoints
 RELAYS="wss://relay.primal.net,wss://relay.damus.io,wss://relay.nostr.band,wss://relayable.org"
.github/workflows/setup.yml (3)

25-37: Quote variables to prevent word splitting
Wrap shell variables in quotes to avoid globbing and word splitting.

-if [[ $KV_OUTPUT == *"already exists"* ]]; then
+if [[ "$KV_OUTPUT" == *"already exists"* ]]; then
...
-echo "KV_ID=$KV_ID" >> $GITHUB_ENV
+echo "KV_ID=$KV_ID" >> "$GITHUB_ENV"
🧰 Tools
🪛 actionlint (1.7.7)

25-25: shellcheck reported issue in this script: SC2086:info:14:24: Double quote to prevent globbing and word splitting

(shellcheck)

🪛 YAMLlint (1.37.1)

[error] 27-27: trailing spaces

(trailing-spaces)


[error] 31-31: trailing spaces

(trailing-spaces)


26-27: Remove trailing spaces and ensure newline at EOF
YAML linting flags trailing spaces on several lines and a missing newline at end of file. Please remove the extra spaces and add a final newline.

Also applies to: 31-32, 38-39, 45-46, 51-52

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 27-27: trailing spaces

(trailing-spaces)


26-27: Use working-directory instead of cd
Leverage the working-directory attribute in your step definitions to avoid repeated cd commands. For example:

- name: Create KV namespace
  working-directory: worker/cloudflare-worker
  run: |
    echo "Creating KV namespace..."
    ...

Also applies to: 31-32

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 27-27: trailing spaces

(trailing-spaces)

worker/cloudflare-worker/deploy.sh (2)

1-7: Ensure script runs from its own directory
To avoid path issues when executing from elsewhere, add at the top:

cd "$(dirname "$0")"

before any other commands.


109-113: Avoid duplicate wrangler publish calls
Running wrangler publish twice can lead to unnecessary API calls. Consider using a single publish --json invocation and parsing the output for the URL:

RESPONSE=$(wrangler publish --json)
WORKER_URL=$(echo "$RESPONSE" | jq -r '.routes[0]')
.github/workflows/validate.yml (1)

21-22: Remove trailing spaces and add newline at EOF
YAML lint errors indicate trailing spaces and a missing newline. Please clean these up.

Also applies to: 23-24, 29-30, 55-56, 70-71, 74-75

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 21-21: trailing spaces

(trailing-spaces)

worker/cloudflare-worker/QUICKSTART-github-actions.md (4)

11-18: Consistent use of the article "the"
For clarity, add "the" before UI elements:

  • Change "Go to Actions tab" to "Go to the Actions tab"
  • Change "Click green 'Run workflow' button" to "Click the green 'Run workflow' button"

136-138: Use an en dash in ranges
Replace the hyphen in "2-3 minutes" with an en dash for typographical correctness:

-Wait 2-3 minutes for the first crawl
+Wait 2–3 minutes for the first crawl
🧰 Tools
🪛 LanguageTool

[uncategorized] ~136-~136: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... a1b2c3d4e5f6... ### Crawler deployed but no data - Wait 2-3 minutes for the firs...

(COMMA_COMPOUND_SENTENCE_2)


[typographical] ~137-~137: If specifying a range, consider using an en dash instead of a hyphen.
Context: ...### Crawler deployed but no data - Wait 2-3 minutes for the first crawl - Check log...

(HYPHEN_TO_EN)


151-152: Article before "Actions tab"
Change "Go to Actions tab" to "Go to the Actions tab" in the manual deployment section for consistency.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~151-~151: You might be missing the article “the” here.
Context: ...t Trigger deployment anytime: 1. Go to Actions tab 2. Select "Deploy Relay Crawler" 3....

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


40-46: Finalize with newline
Add a blank line at the end of the file to satisfy markdown linters.

worker/cloudflare-worker/setup-github-actions.sh (2)

6-6: Enhance script robustness with strict mode.

Replace the existing set -e with set -euo pipefail to catch undefined variables and pipeline errors, ensuring failures in any command or usage of unset variables abort the script.


78-112: Use a temporary file and ensure cleanup.

Hard-coding kv_ids.txt in the working directory can lead to conflicts. Instead, create a secure temporary file via mktemp, export its path, and trap cleanup on exit:

KV_FILE=$(mktemp)
trap 'rm -f "$KV_FILE"' EXIT
...
echo "${env}_KV_ID=${id}" >> "$KV_FILE"
...
cat "$KV_FILE"
...
cat > relay-crawler-github-config.md << EOF
...
$(cat "$KV_FILE")
...
EOF
🧰 Tools
🪛 Shellcheck (0.10.0)

[warning] 78-78: This redirection doesn't have a command. Move to its command (or use 'true' as no-op).

(SC2188)

NOTIFICATION_ARCHITECTURE.md (1)

15-15: Grammar and clarity improvements.

  • Line 15: Change - Fetches user subscriptions from worker to - Fetches user subscriptions from the Worker API.
  • Line 33: Change - When new post in group: notifies only members of that group to - When a new post in a group: notifies only members of that group.

Also applies to: 33-33

🧰 Tools
🪛 LanguageTool

[uncategorized] ~15-~15: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ....ts`) - Fetches user subscriptions from worker - Monitors specific groups for posts (n...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)

worker/cloudflare-worker/YOLO_DEPLOYMENT.md (1)

174-174: Tone and style adjustments for professionalism.

  • Consider replacing “awesome” on line 174 with a more formal alternative (e.g., “excellent”).
  • On line 187, reduce the number of exclamation marks to improve readability and maintain a professional tone.

Also applies to: 187-187

🧰 Tools
🪛 LanguageTool

[style] ~174-~174: Consider using a more formal and expressive alternative to ‘awesome’.
Context: ... 100ms - 😊 User satisfaction: "This is awesome!" - 🎉 Zero data loss (thanks to event ...

(AWESOME)

RELAY_CRAWLER_DEPLOYMENT_CHECKLIST.md (1)

31-31: Minor grammar enhancements.

  • Line 31: Rephrase “Only need the [account-name] part” to “Only the [account-name] part is needed” or “Need only the [account-name] part.”
  • Line 109: Change “Go to Actions tab” to “Go to the Actions tab.”

Also applies to: 109-109

🧰 Tools
🪛 LanguageTool

[grammar] ~31-~31: Consider using either the past participle “needed” or the present participle “needing” here.
Context: ... [account-name].workers.dev - Only need the [account-name] part 3. **Create ...

(BEEN_PART_AGREEMENT)

setup-cloudflare-secrets.sh (1)

6-6: Enhance script robustness with strict mode.

Add set -euo pipefail after the shebang to ensure the script exits on unset variables and pipeline failures:

-#!/bin/bash
+#!/bin/bash
+set -euo pipefail
complete-relay-setup.sh (1)

6-7: Enhance error handling with pipefail

Add set -o pipefail alongside set -e to ensure failures in piped commands are caught:

-set -e
+set -e
+set -o pipefail
check-relay-status.sh (1)

1-2: Recommend adding set -e

Fail early on unexpected errors to avoid false success reports:

 #!/bin/bash
+set -e
setup-github-actions.sh (3)

6-10: Consider adding set -o pipefail

Enhance error detection within pipelines by adding:

 set -e
+set -o pipefail

61-68: Ensure jq is installed before use

The generated script uses jq to parse JSON; add a check or installation step:

if ! command -v jq &> /dev/null; then
    echo "Installing jq..."
    brew install jq   # or `apt-get install jq` for Linux
fi

73-76: Ensure temporary file cleanup on errors

Use a trap to remove vapid-temp.txt if the script exits unexpectedly:

trap 'rm -f vapid-temp.txt' EXIT
notification-bot/enhanced-notification-bot.ts (2)

417-428: processedEvents trimming is O(n²) – use a queue / RingBuffer

Removing the first 5 000 ids from a Set on every 10 000th insert (slice + individual delete) is quadratic and will stall the bot under high throughput.
Maintain a fixed-length queue (e.g. Deque) or a simple circular buffer to ensure O(1) eviction.


469-484: response.ok already covers 204 – condition is redundant

Since Response.ok is true for any 2xx status, including 204, the second check && response.status !== 204 will never be reached.
If you intend to accept 204 only, invert the logic:

-if (!response.ok && response.status !== 204) {
+if (!(response.ok || response.status === 204)) {
   …
 }
src/pages/settings/EnhancedNotifications.tsx (3)

42-57: Side-effect loop risk when marking notifications as read

notifications is in the dependency array; every time it updates the effect fires, potentially triggering another markAsRead → backend update → refetch → state change → effect …

It currently works because the second pass finds all items already read, but it still performs an unnecessary extra render & network call.
Prefer an effect that runs once per unread-count change:

const unread = notifications.filter(n => !n.read);
useEffect(() => {
  if (unread.length === 0) return;
  unread.forEach(n => markAsRead(n.id));
}, [unread.length]);

221-240: Uncontrolled Switch toggles cause stale UI when mutation fails

toggleGroupSubscription(groupId) is async but the UI flips immediately.
If the network request fails the visual state will be out of sync with the backend.

Consider optimistic-but-revert semantics or waiting for the promise:

onCheckedChange={async () => {
  const ok = await toggleGroupSubscription(groupId);
  if (!ok) {/* revert or toast error */}
}}

297-316: Permissions flow: no UX feedback while subscribing / unsubscribing

pushLoading disables the main switch, but the user receives no feedback (spinner, toast).
Adding a small spinner or disabling the entire card while the promise resolves will improve UX.

notification-bot/notification-bot.ts (1)

30-34: botPrivateKey is never used

The field is declared (lines 30-34) and passed via CLI defaults (371-373) but never referenced.
Drop it or, if signing is required later, implement the logic.

Also applies to: 371-373

worker/cloudflare-worker/src/worker-api.ts (3)

44-48: Use 204 (No Content) for pre-flight responses instead of an empty 200

The CORS pre-flight should return a 204 status code by convention; it avoids ambiguous success semantics and saves a few bytes over the wire.

- return new Response(null, { headers: corsHeaders });
+ return new Response(null, { status: 204, headers: corsHeaders });

119-125: Run group-membership updates in parallel for large subscriptions

awaiting each KV write sequentially will add N × RTT latency where N = number of groups.
Gather the promises and await Promise.all to cut request time dramatically for users
subscribed to many groups.

-for (const groupId of userSub.groups) {
-  await this.addUserToGroup(body.npub, groupId);
-}
+await Promise.all(
+  userSub.groups.map(g => this.addUserToGroup(body.npub, g)),
+);

437-440: Non-FCM endpoints will silently drop notifications – implement proper Web Push encryption

The current branch merely logs a message. This means Firefox, Safari, and any
non-FCM Chrome endpoints will never get notifications.

Consider integrating @web-push-libs/web-push (VAPID) or Cloudflare’s
WebPush API to encrypt and send payloads correctly.

worker/cloudflare-worker/README-github-actions.md (1)

155-166: Replace hyphen with en dash in numeric ranges for typographic clarity

Example:

-1-2 minutes
+1–2 minutes
worker/cloudflare-worker/RELAY-CRAWLER-SETUP-COMPLETE.md (1)

87-92: Grammar tweak – add “to” before infinitive

-Add authentication tokens for security
+Add authentication tokens to improve security
🧰 Tools
🪛 LanguageTool

[uncategorized] ~88-~88: The preposition ‘to’ seems more likely in this position.
Context: ...e configuration**: - Edit relay URLs in wrangler-crawler.toml - Adjust cro...

(AI_HYDRA_LEO_REPLACE_IN_TO)


[grammar] ~89-~89: The verb “Add” needs to be in the to-infinitive form.
Context: ...wler.toml` - Adjust cron schedule if needed - Add authentication tokens for security 3. ...

(MISSING_TO_BEFORE_A_VERB)

src/hooks/usePushNotifications.ts (1)

284-290: Possible race condition when toggling group subscriptions

Because settings.subscribedGroups comes from a stale render, two rapid clicks
can overwrite each other. Use a functional update and delegate persistence to
updateSettings.

-const toggleGroupSubscription = useCallback((groupId: string) => {
-  const newGroups = settings.subscribedGroups.includes(groupId)
-    ? settings.subscribedGroups.filter(id => id !== groupId)
-    : [...settings.subscribedGroups, groupId];
-  
-  updateSettings({ subscribedGroups: newGroups });
-}, [settings.subscribedGroups, updateSettings]);
+const toggleGroupSubscription = useCallback((groupId: string) => {
+  setSettings(prev => {
+    const newGroups = prev.subscribedGroups.includes(groupId)
+      ? prev.subscribedGroups.filter(id => id !== groupId)
+      : [...prev.subscribedGroups, groupId];
+    updateSettings({ subscribedGroups: newGroups });
+    return { ...prev, subscribedGroups: newGroups };
+  });
+}, [updateSettings]);
.github/workflows/deploy.yml (2)

140-147: Trailing spaces & missing final newline

Lines around 144 contain trailing whitespace, and the file lacks a newline at EOF (error new-line-at-end-of-file). Although minor, these lint violations can trip pre-commit hooks.

-          docker save chorus-notification-bot > bot-image.tar␠␠
+          docker save chorus-notification-bot > bot-image.tar

Add a blank line at the end of the file to silence YAMLlint.

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 144-144: trailing spaces

(trailing-spaces)


100-108: Potential brittle path – cp ../bot-package.json

build-bot copies ../bot-package.json into notification-bot.
If a contributor runs the job from a shallow-checkout or changes directory layout, the copy will fail. Consider one of:

  1. Keep a dedicated package.json inside notification-bot/ and remove the cp.
  2. Use --path of actions/checkout to fetch only required sub-folders.

This improves reproducibility of the CI job.

worker/cloudflare-worker/src/relay-monitor-worker.ts (1)

106-113: KV write amplification risk

processed:${event.id} keys are written for every event with a 24 h TTL.
High-traffic relays may create millions of keys/day → increased KV costs and latency.

Consider an LRU in-memory Bloom filter (cf. tiny-bloom-filter) or batch a single sorted-set style key per day (e.g. processed:2024-05-27) to reduce writes.

NOTIFICATION_IMPLEMENTATION.md (1)

46-55: Remove trailing punctuation from headings to satisfy markdownlint and keep style consistent

markdownlint (rule MD026) warns about headings ending with :.
Removing the colon has no semantic impact and avoids style-checker noise.

-### For Users:
+### For Users-### For Deployment:
+### For Deployment
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

46-46: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


55-55: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

worker/cloudflare-worker/src/worker-all-in-one.ts (2)

189-197: Relay polling is sequential – use Promise.all to reduce wall-clock time

The current for … await waits for each relay before starting the next, multiplying latency.

-    for (const relayUrl of relayUrls) {
-      try {
-        const relayEvents = await this.fetchFromSingleRelay(relayUrl, filters);
-        events.push(...relayEvents);
-      } catch (error) {
-        console.error(`Error fetching from ${relayUrl}:`, error);
-      }
-    }
+    await Promise.all(
+      relayUrls.map(async (relayUrl) => {
+        try {
+          const relayEvents = await this.fetchFromSingleRelay(relayUrl, filters);
+          events.push(...relayEvents);
+        } catch (error) {
+          console.error(`Error fetching from ${relayUrl}:`, error);
+        }
+      })
+    );

311-312: Optional chaining prevents crashes when user is undefined

-          if (user && user.preferences?.reactions) {
+          if (user?.preferences?.reactions) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 311-312: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

worker/cloudflare-worker/NOTIFICATION_PLAN.md (1)

220-223: Minor grammar – possessive apostrophe missing

“users timezone” → “users’ timezone” (or “user’s”).
Not crucial but improves clarity.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~220-~220: It looks like there might be a possessive apostrophe missing. Consider inserting it.
Context: ...ons ### 5.2 Smart Quiet Hours - Detect user timezone from activity patterns - Respe...

(AI_EN_LECTOR_MISSING_NOUN_POSSESSIVE)

worker/cloudflare-worker/src/worker-api-enhanced.ts (1)

351-358: PII stored unencrypted – consider hashing endpoint & keys
queuePushNotification() persists the full endpoint, p256dh, and auth keys together with the payload. These fields are personally-identifiable and, if leaked, can be abused for unsolicited pushes.

Recommendation:

-      JSON.stringify({ subscription, payload, attempts: 0 }),
+      JSON.stringify({
+        subscription: {
+          endpointHash: await crypto.subtle.digest('SHA-256', new TextEncoder().encode(subscription.endpoint)),
+          keys: {
+            p256dh: subscription.keys.p256dh,
+            auth: subscription.keys.auth
+          }
+        },
+        payload,
+        attempts: 0
+      }),

(or at minimum store the endpoint hash and retrieve the full subscription from sub:<npub> when dispatching).

worker/cloudflare-worker/src/push-notification-worker.ts (1)

392-401: Unused variable – authorNpub is computed but never referenced
authorNpub is created via nip19.npubEncode but not used anywhere in buildPushPayload().
Delete it or integrate it into the notification body / data to avoid dead code and bundle bloat.

worker/cloudflare-worker/src/worker-enhanced.ts (1)

238-258: Multiple awaited KV writes in tight loops – consider batching
handleUserRegistration() updates keyword and group subscriber lists with await inside each iteration. With many keywords/groups this can exceed execution time.

Suggestion: build the updated arrays in memory and perform a single env.KV.put per key using Promise.all or ctx.waitUntil to parallelise writes.

RELEASE_CHECKLIST.md (4)

25-26: Add missing article for consistency in bullet items

For clarity, include “the” before “latest version” and “SimplePool API calls”:

- [ ] Update nostr-tools to latest version
+ [ ] Update nostr-tools to the latest version
- [ ] Fix SimplePool API calls (use `subscribeMany` not `sub`)
+ [ ] Fix the SimplePool API calls (use `subscribeMany` not `sub`)
🧰 Tools
🪛 LanguageTool

[uncategorized] ~25-~25: You might be missing the article “the” here.
Context: ...mentation** - [ ] Update nostr-tools to latest version - [ ] Fix SimplePool API calls ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


88-89: Use an en dash for numeric ranges

Replace the hyphen in “24-48 hours” with an en dash for typographical correctness:

- Monitor for 24-48 hours
+ Monitor for 24 – 48 hours
🧰 Tools
🪛 LanguageTool

[typographical] ~89-~89: If specifying a range, consider using an en dash instead of a hyphen.
Context: ...with small group of users - Monitor for 24-48 hours - Check error logs and metrics #...

(HYPHEN_TO_EN)


131-134: Remove trailing colons from headings

Markdown headings shouldn’t end with punctuation. Update these lines:

- ## Go/No-Go Criteria:
+ ## Go/No-Go Criteria
- ### ✅ Ready for Release When:
+ ### ✅ Ready for Release When
🧰 Tools
🪛 LanguageTool

[uncategorized] ~134-~134: This verb may not be in the correct tense. Consider changing the tense to fit the context better.
Context: ...for Release When: 1. All critical items complete 2. Security audit passed 3. Load testin...

(AI_EN_LECTOR_REPLACEMENT_VERB_TENSE)

🪛 markdownlint-cli2 (0.17.2)

133-133: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


141-142: Remove trailing colon from heading

Similarly, drop the colon here:

- ### ❌ Block Release If:
+ ### ❌ Block Release If
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

141-141: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

PUSH_NOTIFICATIONS.md (3)

102-111: Specify language for ASCII diagram code block

The architecture diagram is in a fenced block without a language. For proper linting, add a hint such as text or ascii:

- ```
+ ```text
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Browser   │───▶│   Worker     │◀───│     Bot     │
│   SW.js     │    │ - Store subs │    │ - Monitor   │
│   Push API  │    │ - Queue msgs │    │   relays    │
└─────────────┘    └──────────────┘    └─────────────┘
                      │
                      ▼
                  ┌──────────────┐
                  │ Push Service │
                  │   (FCM/APN)  │
                  └──────────────┘
- ```
+ ```

93-93: Add missing article for clarity

Include “the” before “notification settings page”:

- 1. User visits notification settings page
+ 1. User visits the notification settings page
🧰 Tools
🪛 LanguageTool

[uncategorized] ~93-~93: You might be missing the article “the” here.
Context: ...start ``` ## User Flow 1. User visits notification settings page 2. Clicks "Enable Push No...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


96-96: Include an article for grammatical correctness

Add “a” before “subscription” and “the” before “worker”:

- 4. If granted, subscription is created and sent to worker
+ 4. If granted, a subscription is created and sent to the worker
🧰 Tools
🪛 LanguageTool

[uncategorized] ~96-~96: You might be missing the article “a” here.
Context: ...wser asks for permission 4. If granted, subscription is created and sent to worker 5. Bot mo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)

worker/IMPLEMENTATION.md (3)

7-11: Specify language for ASCII diagram code block

The high-level architecture diagram is presented in a generic fenced block. Add a language marker (text or ascii) to satisfy markdownlint:

- ```
+ ```text
Nostr Relays → Cloudflare Worker → Push API → Web Push Service → Browsers
                      ↓                ↓
                   KV Storage    PostgreSQL DB
- ```
+ ```
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

7-7: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


111-115: Add language hint to wrangler.toml snippet

The Cloudflare Worker environment variables block is missing a language. Use toml for clarity:

- ```
+ ```toml
RELAY_URL=wss://relay.chorus.community/
PUSH_DISPATCH_API=https://your-api.com
BOT_TOKEN=same-as-push-api
- ```
+ ```
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

111-111: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


217-217: Include the definite article for consistency

Prepend “the” before “environment”:

- - VAPID keys stored securely in environment
+ - VAPID keys stored securely in the environment
🧰 Tools
🪛 LanguageTool

[uncategorized] ~217-~217: You might be missing the article “the” here.
Context: ...ations - VAPID keys stored securely in environment - Bot token authentication for internal...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbbc57b and adaba25.

📒 Files selected for processing (50)
  • .env.example (1 hunks)
  • .github/workflows/README.md (1 hunks)
  • .github/workflows/deploy.yml (1 hunks)
  • .github/workflows/setup.yml (1 hunks)
  • .github/workflows/validate.yml (1 hunks)
  • DEPLOYMENT.md (1 hunks)
  • DEPLOYMENT_GUIDE.md (1 hunks)
  • NOTIFICATION_ARCHITECTURE.md (1 hunks)
  • NOTIFICATION_IMPLEMENTATION.md (1 hunks)
  • PUSH_NOTIFICATIONS.md (1 hunks)
  • RELAY_CRAWLER_DEPLOYMENT_CHECKLIST.md (1 hunks)
  • RELAY_CRAWLER_DEPLOYMENT_STATUS.md (1 hunks)
  • RELEASE_CHECKLIST.md (1 hunks)
  • bot-package.json (1 hunks)
  • check-relay-status.sh (1 hunks)
  • complete-relay-setup.sh (1 hunks)
  • deploy-bot.sh (1 hunks)
  • generate-vapid-keys.sh (1 hunks)
  • manual-kv-setup.sh (1 hunks)
  • notification-bot/.env.example (1 hunks)
  • notification-bot/enhanced-notification-bot.ts (1 hunks)
  • notification-bot/notification-bot.ts (1 hunks)
  • push-service/server.js (1 hunks)
  • relay-crawler-kv-config.txt (1 hunks)
  • setup-cloudflare-secrets.sh (1 hunks)
  • setup-github-actions.sh (1 hunks)
  • src/AppRouter.tsx (1 hunks)
  • src/hooks/usePushNotifications.ts (1 hunks)
  • src/pages/settings/EnhancedNotifications.tsx (1 hunks)
  • worker/IMPLEMENTATION.md (1 hunks)
  • worker/VAPID_KEYS.md (1 hunks)
  • worker/cloudflare-worker/DEPLOYMENT_STATUS.md (1 hunks)
  • worker/cloudflare-worker/NOTIFICATION_PLAN.md (1 hunks)
  • worker/cloudflare-worker/QUICKSTART-github-actions.md (1 hunks)
  • worker/cloudflare-worker/QUICK_DEPLOY.md (1 hunks)
  • worker/cloudflare-worker/README-github-actions.md (1 hunks)
  • worker/cloudflare-worker/RELAY-CRAWLER-SETUP-COMPLETE.md (1 hunks)
  • worker/cloudflare-worker/YOLO_DEPLOYMENT.md (1 hunks)
  • worker/cloudflare-worker/cloudflare-config.md (1 hunks)
  • worker/cloudflare-worker/deploy-crawler.sh (1 hunks)
  • worker/cloudflare-worker/deploy.sh (1 hunks)
  • worker/cloudflare-worker/setup-github-actions.sh (1 hunks)
  • worker/cloudflare-worker/src/push-notification-worker.ts (1 hunks)
  • worker/cloudflare-worker/src/relay-monitor-worker.ts (1 hunks)
  • worker/cloudflare-worker/src/worker-all-in-one.ts (1 hunks)
  • worker/cloudflare-worker/src/worker-api-enhanced.ts (1 hunks)
  • worker/cloudflare-worker/src/worker-api.ts (1 hunks)
  • worker/cloudflare-worker/src/worker-enhanced.ts (1 hunks)
  • worker/cloudflare-worker/wrangler-crawler.toml (1 hunks)
  • worker/cloudflare-worker/wrangler.toml (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • RELAY_CRAWLER_DEPLOYMENT_STATUS.md
  • bot-package.json
  • DEPLOYMENT.md
  • notification-bot/.env.example
  • DEPLOYMENT_GUIDE.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • .env.example
  • worker/cloudflare-worker/wrangler-crawler.toml
  • worker/cloudflare-worker/DEPLOYMENT_STATUS.md
🧰 Additional context used
🧬 Code Graph Analysis (1)
worker/cloudflare-worker/src/worker-enhanced.ts (7)
worker/cloudflare-worker/src/push-notification-worker.ts (2)
  • Env (8-14)
  • handleTestNotification (268-299)
worker/cloudflare-worker/src/worker-all-in-one.ts (1)
  • Env (10-17)
worker/cloudflare-worker/src/relay-monitor-worker.ts (1)
  • Env (8-13)
worker/deploy/user-registration.js (4)
  • request (183-183)
  • request (191-191)
  • request (199-199)
  • request (208-208)
worker/cloudflare-worker/src/worker-api.ts (2)
  • WorkerAPI (31-441)
  • handleTestNotification (281-306)
worker/cloudflare-worker/src/worker-api-enhanced.ts (2)
  • WorkerAPI (35-364)
  • handleTestNotification (247-269)
worker/cloudflare-worker/src/notification-system.ts (3)
  • NotificationSystem (54-410)
  • NOTIFICATION_EVENT_KINDS (9-16)
  • UserPreferences (28-52)
🪛 Shellcheck (0.10.0)
worker/cloudflare-worker/setup-github-actions.sh

[warning] 78-78: This redirection doesn't have a command. Move to its command (or use 'true' as no-op).

(SC2188)

manual-kv-setup.sh

[warning] 18-18: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 19-19: Declare and assign separately to avoid masking return values.

(SC2155)


[warning] 113-113: PROD_PREVIEW_ID appears unused. Verify use (or export if used externally).

(SC2034)

🪛 LanguageTool
worker/cloudflare-worker/YOLO_DEPLOYMENT.md

[style] ~174-~174: Consider using a more formal and expressive alternative to ‘awesome’.
Context: ... 100ms - 😊 User satisfaction: "This is awesome!" - 🎉 Zero data loss (thanks to event ...

(AWESOME)


[style] ~187-~187: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 1499 characters long)
Context: ...wrangler rollback` Happy YOLO deploying! 🎊

(EN_EXCESSIVE_EXCLAMATION)

NOTIFICATION_ARCHITECTURE.md

[uncategorized] ~15-~15: The grammatical number of this noun doesn’t look right. Consider replacing it.
Context: ....ts`) - Fetches user subscriptions from worker - Monitors specific groups for posts (n...

(AI_EN_LECTOR_REPLACEMENT_NOUN_NUMBER)


[duplication] ~24-~24: Possible typo: you repeated a word.
Context: ... - Browser sends push subscription to worker - Worker stores user's npub, groups, and prefere...

(ENGLISH_WORD_REPEAT_RULE)


[grammar] ~27-~27: It seems that the correct verb form here is “relay”.
Context: ...preferences in KV 2. Bot monitors relays: - Loads user subscriptions from w...

(AGREEMENT_SENT_START_2)


[uncategorized] ~33-~33: You might be missing the article “a” here.
Context: ...ent processing**: - When new post in group: notifies only members of that group ...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~128-~128: You might be missing the article “the” here.
Context: ...ne** - they get notified even if not in group 5. React to a post - author gets no...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

PUSH_NOTIFICATIONS.md

[uncategorized] ~93-~93: You might be missing the article “the” here.
Context: ...start ``` ## User Flow 1. User visits notification settings page 2. Clicks "Enable Push No...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~96-~96: You might be missing the article “a” here.
Context: ...wser asks for permission 4. If granted, subscription is created and sent to worker 5. Bot mo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~98-~98: You might be missing the article “the” here.
Context: ...r receives push notifications even when app is closed ## Architecture ``` ┌──────...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

RELEASE_CHECKLIST.md

[uncategorized] ~25-~25: You might be missing the article “the” here.
Context: ...mentation** - [ ] Update nostr-tools to latest version - [ ] Fix SimplePool API calls ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[typographical] ~89-~89: If specifying a range, consider using an en dash instead of a hyphen.
Context: ...with small group of users - Monitor for 24-48 hours - Check error logs and metrics #...

(HYPHEN_TO_EN)


[uncategorized] ~134-~134: This verb may not be in the correct tense. Consider changing the tense to fit the context better.
Context: ...for Release When: 1. All critical items complete 2. Security audit passed 3. Load testin...

(AI_EN_LECTOR_REPLACEMENT_VERB_TENSE)


[grammar] ~178-~178: Did you mean “to Create”?
Context: ...ures - Scale infrastructure if needed - Create user documentation ### Ongoing: - Secu...

(MISSING_TO_BEFORE_A_VERB)

worker/cloudflare-worker/README-github-actions.md

[typographical] ~175-~175: If specifying a range, consider using an en dash instead of a hyphen.
Context: ...ct 3. Deployment URL 404 - Wait 1-2 minutes for deployment to propagate ...

(HYPHEN_TO_EN)

worker/IMPLEMENTATION.md

[style] ~147-~147: Consider using an alternative to strengthen your wording.
Context: ...roups - Join Requests: When someone wants to join user's groups - Reports: Co...

(WANT_KEEN)


[uncategorized] ~217-~217: You might be missing the article “the” here.
Context: ...ations - VAPID keys stored securely in environment - Bot token authentication for internal...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

worker/cloudflare-worker/NOTIFICATION_PLAN.md

[uncategorized] ~220-~220: It looks like there might be a possessive apostrophe missing. Consider inserting it.
Context: ...ons ### 5.2 Smart Quiet Hours - Detect user timezone from activity patterns - Respe...

(AI_EN_LECTOR_MISSING_NOUN_POSSESSIVE)

worker/cloudflare-worker/RELAY-CRAWLER-SETUP-COMPLETE.md

[uncategorized] ~32-~32: Loose punctuation mark.
Context: ...ion Files - wrangler-crawler.toml: Cloudflare Worker configuration - **`de...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~39-~39: Loose punctuation mark.
Context: ...ntation - README-relay-crawler.md: Complete relay crawler documentation - ...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~88-~88: The preposition ‘to’ seems more likely in this position.
Context: ...e configuration**: - Edit relay URLs in wrangler-crawler.toml - Adjust cro...

(AI_HYDRA_LEO_REPLACE_IN_TO)


[grammar] ~89-~89: The verb “Add” needs to be in the to-infinitive form.
Context: ...wler.toml` - Adjust cron schedule if needed - Add authentication tokens for security 3. ...

(MISSING_TO_BEFORE_A_VERB)

.github/workflows/README.md

[grammar] ~121-~121: Did you mean “to Verify”?
Context: ... 2. Clear Cloudflare cache if needed 3. Verify the worker URL in production ## Local ...

(MISSING_TO_BEFORE_A_VERB)


[typographical] ~169-~169: It seems that a comma is missing.
Context: ... GB free - Estimated monthly usage: Well within free tiers ## Security Notes -...

(WELL_COMMA)


[uncategorized] ~176-~176: A period might be missing here.
Context: ...larly - VAPID keys should be unique per environment ## Future Enhancements - [ ] Add stag...

(AI_EN_LECTOR_MISSING_PUNCTUATION_PERIOD)

RELAY_CRAWLER_DEPLOYMENT_CHECKLIST.md

[grammar] ~31-~31: Consider using either the past participle “needed” or the present participle “needing” here.
Context: ... [account-name].workers.dev - Only need the [account-name] part 3. **Create ...

(BEEN_PART_AGREEMENT)


[uncategorized] ~109-~109: You might be missing the article “the” here.
Context: ...1. Check GitHub Actions: - Go to Actions tab - Watch the "Deploy Relay Crawle...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~172-~172: Did you mean “to Configure”?
Context: ...board 2. Adjust relay URLs if needed 3. Configure authentication tokens for security 4. I...

(MISSING_TO_BEFORE_A_VERB)

relay-crawler-kv-config.txt

[uncategorized] ~23-~23: A period might be missing here.
Context: ..._SUBDOMAIN.workers.dev # - Development: https://relay-crawler.YOUR_SUBDOMAIN.workers.dev

(AI_EN_LECTOR_MISSING_PUNCTUATION_PERIOD)

worker/VAPID_KEYS.md

[style] ~44-~44: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 650 characters long)
Context: ...``` Never log or expose the private key!

(EN_EXCESSIVE_EXCLAMATION)

worker/cloudflare-worker/QUICKSTART-github-actions.md

[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ...3: Run Setup Workflow (1 min) 1. Go to Actions tab in your GitHub repo 2. Find "Setup ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~52-~52: You might be missing the article “the” here.
Context: .... Keep both checkboxes checked 5. Click green "Run workflow" button Wait ~30 seconds...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~136-~136: Use a comma before ‘but’ if it connects two independent clauses (unless they are closely connected and short).
Context: ... a1b2c3d4e5f6... ### Crawler deployed but no data - Wait 2-3 minutes for the firs...

(COMMA_COMPOUND_SENTENCE_2)


[typographical] ~137-~137: If specifying a range, consider using an en dash instead of a hyphen.
Context: ...### Crawler deployed but no data - Wait 2-3 minutes for the first crawl - Check log...

(HYPHEN_TO_EN)


[uncategorized] ~151-~151: You might be missing the article “the” here.
Context: ...t Trigger deployment anytime: 1. Go to Actions tab 2. Select "Deploy Relay Crawler" 3....

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

worker/cloudflare-worker/QUICK_DEPLOY.md

[grammar] ~74-~74: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...s support - Rate limiting 3. YOLO Testing - Test on live relays - Auto-cleanup with k...

(REPEATED_VERBS)

🪛 actionlint (1.7.7)
.github/workflows/deploy.yml

191-191: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)


196-196: property "build-app" is not defined in object type {build-bot: {outputs: {}; result: string}; deploy-worker: {outputs: {}; result: string}}

(expression)

.github/workflows/setup.yml

25-25: shellcheck reported issue in this script: SC2086:info:14:24: Double quote to prevent globbing and word splitting

(shellcheck)

🪛 YAMLlint (1.37.1)
.github/workflows/deploy.yml

[error] 144-144: trailing spaces

(trailing-spaces)


[error] 201-201: no new line character at the end of file

(new-line-at-end-of-file)

.github/workflows/setup.yml

[error] 27-27: trailing spaces

(trailing-spaces)


[error] 31-31: trailing spaces

(trailing-spaces)


[error] 38-38: trailing spaces

(trailing-spaces)


[error] 45-45: trailing spaces

(trailing-spaces)


[error] 51-51: trailing spaces

(trailing-spaces)


[error] 61-61: no new line character at the end of file

(new-line-at-end-of-file)

.github/workflows/validate.yml

[error] 21-21: trailing spaces

(trailing-spaces)


[error] 23-23: trailing spaces

(trailing-spaces)


[error] 29-29: trailing spaces

(trailing-spaces)


[error] 55-55: trailing spaces

(trailing-spaces)


[error] 70-70: trailing spaces

(trailing-spaces)


[error] 74-74: trailing spaces

(trailing-spaces)


[error] 75-75: no new line character at the end of file

(new-line-at-end-of-file)

🪛 markdownlint-cli2 (0.17.2)
NOTIFICATION_IMPLEMENTATION.md

46-46: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


55-55: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

RELEASE_CHECKLIST.md

133-133: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


141-141: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


150-150: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


161-161: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


168-168: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


174-174: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


180-180: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

worker/IMPLEMENTATION.md

7-7: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


99-99: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


111-111: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

worker/cloudflare-worker/cloudflare-config.md

7-7: Bare URL used
null

(MD034, no-bare-urls)


11-11: Bare URL used
null

(MD034, no-bare-urls)


15-15: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


25-25: Bare URL used
null

(MD034, no-bare-urls)


26-26: Bare URL used
null

(MD034, no-bare-urls)


27-27: Bare URL used
null

(MD034, no-bare-urls)

🪛 GitHub Actions: Validate Environment
worker/cloudflare-worker/wrangler.toml

[error] 1-1: KV namespace ID not configured in wrangler.toml. Run the 'Setup Cloudflare Infrastructure' workflow first.

🪛 Biome (1.9.4)
worker/cloudflare-worker/src/worker-all-in-one.ts

[error] 271-272: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 306-307: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 311-312: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

worker/cloudflare-worker/src/relay-monitor-worker.ts

[error] 43-43: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (20)
src/AppRouter.tsx (1)

18-18:

✅ Verification successful

LGTM! Component upgrade aligns with PR objectives.

The import path change from Notifications to EnhancedNotifications correctly integrates the new enhanced notifications UI mentioned in the PR summary.

Verify that the new component exists:


🏁 Script executed:

#!/bin/bash
# Verify the EnhancedNotifications component exists
fd "EnhancedNotifications" --type f --exec ls -la

Length of output: 146


EnhancedNotifications component verified

  • The new component exists at src/pages/settings/EnhancedNotifications.tsx.
  • The lazy import in src/AppRouter.tsx (line 18) correctly references this file.

All changes align with the PR objectives—no further action required.

worker/cloudflare-worker/QUICK_DEPLOY.md (1)

1-99: Verify relevance to PR objectives and content mismatch
This guide appears to describe the v2.0 "YOLO" push notification system rather than the relay crawler worker targeted by this PR. Please confirm whether QUICK_DEPLOY.md belongs in this PR or was added by mistake.

🧰 Tools
🪛 LanguageTool

[grammar] ~74-~74: You’ve repeated a verb. Did you mean to only write one of them?
Context: ...s support - Rate limiting 3. YOLO Testing - Test on live relays - Auto-cleanup with k...

(REPEATED_VERBS)

worker/cloudflare-worker/deploy.sh (1)

22-33: ⚠️ Potential issue

Check for jq before using it
The script uses jq to parse JSON but does not install or verify its presence. Add a check and install step:

+if ! command -v jq &> /dev/null; then
+  echo "❌ jq not found. Installing..."
+  npm install -g jq
+fi

Likely an incorrect or invalid review comment.

.github/workflows/README.md (1)

7-29: ⚠️ Potential issue

Documentation workflows mismatch new filenames

The README refers to deploy.yml, setup.yml, and validate.yml, but this PR adds deploy-relay-crawler.yml and setup-relay-crawler.yml (and no validate.yml). Update workflow names, triggers, and descriptions to match the actual files in .github/workflows/.

Likely an incorrect or invalid review comment.

complete-relay-setup.sh (3)

49-65: Required files existence check looks good

Looping through an array to verify presence of all critical workflow and worker files is clear and maintainable.


98-135: Interactive secret configuration is comprehensive

The script checks existing GitHub Actions secrets and interactively sets any that are missing, with clear user prompts.


197-216: Next steps guidance is clear and actionable

The final summary accurately directs users on PR creation, workflow triggering, artifact handling, and configuration updates.

manual-kv-setup.sh (1)

35-98: Namespace creation logic is robust

The create_namespace function correctly handles both existing and new namespaces (including preview variants) and falls back to multiple patterns for ID extraction, improving resilience against Wrangler output changes.

check-relay-status.sh (5)

19-33: File existence checks are clear

Using an associative array of file paths and descriptions with color-coded output makes it easy to spot missing components.


36-48: Git status inspection covers staged/uncommitted cases well

The script distinguishes between staged, unstaged, and fully committed relay-crawler-related changes, helping users know exactly where they stand.


54-82: GitHub secrets verification is comprehensive

Optional and required secrets are clearly listed, and both authentication and CLI absence cases are handled gracefully.


90-99: KV configuration status detection is straightforward

A simple grep over placeholders lets users know if they need to run the setup workflow.


105-119: Summary steps are succinct and helpful

Tailored next-step instructions based on the current branch context guide users through completing the relay crawler setup.

setup-github-actions.sh (3)

34-52: Encapsulated secret-setting function improves reuse

The set_secret helper centralizes secret creation with customizable descriptions and interactive fallback, enhancing maintainability.


108-115: Production environment file generation is correct

The heredoc creates a .env.production with placeholders and instructions, matching expected usage.


121-135: Final user instructions are clear and actionable

Steps for committing, pushing, and triggering the setup workflow are laid out cleanly for end users.

worker/cloudflare-worker/src/worker-api.ts (1)

420-427: Guard against missing FCM_SERVER_KEY to avoid sending an Authorization: key=undefined header

When FCM_SERVER_KEY is not configured the request will still carry an
invalid header, causing a 401 and noisy logs.

-        'Authorization': `key=${this.env.FCM_SERVER_KEY}`, // Optional: for better delivery
+        ...(this.env.FCM_SERVER_KEY
+            ? { 'Authorization': `key=${this.env.FCM_SERVER_KEY}` }
+            : {}),
src/hooks/usePushNotifications.ts (1)

74-83: checkSubscription may hang if the service worker never becomes “ready”

If the SW fails to register, navigator.serviceWorker.ready never resolves,
blocking the promise chain forever. Consider a timeout or catching the
TypeError thrown in that scenario to keep the UI responsive.

worker/cloudflare-worker/src/relay-monitor-worker.ts (1)

161-206: WebSocket usage may fail on Cloudflare Workers

Outgoing client WebSockets are still experimental and disabled on the regular Worker runtime. The current code will throw ERR_FEATURE_UNSUPPORTED at runtime.

Alternatives:

  1. Move relay polling to a Durable Object / D1 edge function running on a platform that supports client WebSockets.
  2. Use Nostr over HTTP relay gateways (e.g. https://<relay>/events?since=…) if available.

Verify runtime support before merging.

worker/IMPLEMENTATION.md (1)

75-86: SQL schema snippet is correctly fenced

The PostgreSQL migration commands are properly marked with sql, making this snippet clear and lint-compliant.

Comment on lines +7 to +11
# Check if web-push is installed
if ! command -v web-push &> /dev/null; then
echo "📦 Installing web-push CLI..."
npm install -g web-push
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add dependency checks for required tools.

The script uses jq and openssl but doesn't verify they're installed. Also, global npm installation may require sudo permissions.

 # Check if web-push is installed
 if ! command -v web-push &> /dev/null; then
     echo "📦 Installing web-push CLI..."
-    npm install -g web-push
+    if ! npm install -g web-push; then
+        echo "❌ Failed to install web-push. You may need sudo permissions."
+        exit 1
+    fi
 fi
+
+# Check if jq is installed
+if ! command -v jq &> /dev/null; then
+    echo "❌ jq is required but not installed. Please install jq first."
+    echo "On macOS: brew install jq"
+    echo "On Ubuntu/Debian: sudo apt-get install jq"
+    exit 1
+fi
+
+# Check if openssl is installed
+if ! command -v openssl &> /dev/null; then
+    echo "❌ openssl is required but not installed."
+    exit 1
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check if web-push is installed
if ! command -v web-push &> /dev/null; then
echo "📦 Installing web-push CLI..."
npm install -g web-push
fi
# Check if web-push is installed
if ! command -v web-push &> /dev/null; then
echo "📦 Installing web-push CLI..."
if ! npm install -g web-push; then
echo "❌ Failed to install web-push. You may need sudo permissions."
exit 1
fi
fi
# Check if jq is installed
if ! command -v jq &> /dev/null; then
echo "❌ jq is required but not installed. Please install jq first."
echo "On macOS: brew install jq"
echo "On Ubuntu/Debian: sudo apt-get install jq"
exit 1
fi
# Check if openssl is installed
if ! command -v openssl &> /dev/null; then
echo "❌ openssl is required but not installed."
exit 1
fi
🤖 Prompt for AI Agents
In generate-vapid-keys.sh around lines 7 to 11, add checks to verify that both
jq and openssl are installed before proceeding, similar to the existing check
for web-push. For each missing tool, print a clear message prompting the user to
install it. Additionally, modify the npm install command for web-push to include
sudo to handle permission issues during global installation.

Comment on lines +13 to +18
# Generate keys
echo "🔐 Generating new VAPID key pair..."
KEYS=$(web-push generate-vapid-keys --json)

PUBLIC_KEY=$(echo $KEYS | jq -r '.publicKey')
PRIVATE_KEY=$(echo $KEYS | jq -r '.privateKey')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for key generation.

The script doesn't handle potential failures in key generation or JSON parsing.

 # Generate keys
 echo "🔐 Generating new VAPID key pair..."
-KEYS=$(web-push generate-vapid-keys --json)
+if ! KEYS=$(web-push generate-vapid-keys --json); then
+    echo "❌ Failed to generate VAPID keys"
+    exit 1
+fi
 
-PUBLIC_KEY=$(echo $KEYS | jq -r '.publicKey')
-PRIVATE_KEY=$(echo $KEYS | jq -r '.privateKey')
+if ! PUBLIC_KEY=$(echo "$KEYS" | jq -r '.publicKey'); then
+    echo "❌ Failed to extract public key"
+    exit 1
+fi
+
+if ! PRIVATE_KEY=$(echo "$KEYS" | jq -r '.privateKey'); then
+    echo "❌ Failed to extract private key"
+    exit 1
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Generate keys
echo "🔐 Generating new VAPID key pair..."
KEYS=$(web-push generate-vapid-keys --json)
PUBLIC_KEY=$(echo $KEYS | jq -r '.publicKey')
PRIVATE_KEY=$(echo $KEYS | jq -r '.privateKey')
# Generate keys
echo "🔐 Generating new VAPID key pair..."
if ! KEYS=$(web-push generate-vapid-keys --json); then
echo "❌ Failed to generate VAPID keys"
exit 1
fi
if ! PUBLIC_KEY=$(echo "$KEYS" | jq -r '.publicKey'); then
echo "❌ Failed to extract public key"
exit 1
fi
if ! PRIVATE_KEY=$(echo "$KEYS" | jq -r '.privateKey'); then
echo "❌ Failed to extract private key"
exit 1
fi
🤖 Prompt for AI Agents
In generate-vapid-keys.sh around lines 13 to 18, the script lacks error handling
for the key generation and JSON parsing steps. Add checks after running the
web-push generate-vapid-keys command to verify it succeeded, and also validate
that the KEYS variable contains valid JSON before extracting publicKey and
privateKey. If any step fails, output an error message and exit the script with
a non-zero status.

Comment on lines +15 to +28
## Private Key (Server-side) 🔒
**KEEP THIS SECRET! Never commit to repository or expose in client code!**

The private key should be added to your Cloudflare Worker as a secret:

1. Run this command in the worker/cloudflare-worker directory:
```
wrangler secret put VAPID_PRIVATE_KEY
```

2. When prompted, paste this key:
```
j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

CRITICAL SECURITY ISSUE: Private key exposed in documentation.

The documentation contains what appears to be a real VAPID private key. This is a serious security vulnerability that could allow unauthorized access to your push notification service.

Replace the actual private key with a placeholder:

 2. When prompted, paste this key:
    ```
-   j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ
+   YOUR_ACTUAL_PRIVATE_KEY_HERE
    ```

Immediate actions required:

  1. Generate new VAPID keys using the generate-vapid-keys.sh script
  2. Update all environments with the new keys
  3. Revoke the exposed private key if it was being used in production

Check if this private key is currently in use:


🏁 Script executed:

#!/bin/bash
# Search for usage of the exposed private key in the codebase
rg -i "j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ" --type-not md

Length of output: 65


CRITICAL SECURITY ISSUE: Remove exposed VAPID private key from documentation and replace with a placeholder

The file worker/VAPID_KEYS.md currently contains a real VAPID private key, which must never be committed or shared in plaintext.

Affected location:

  • worker/VAPID_KEYS.md (lines 15–28)

Required changes:

 2. When prompted, paste this key:
    ```
-   j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ
+   YOUR_ACTUAL_PRIVATE_KEY_HERE
    ```

Immediate actions:

  1. Replace the hard-coded key in the documentation with a descriptive placeholder.
  2. Run ./generate-vapid-keys.sh (or your key-generation script) to create a new VAPID key pair.
  3. Update all environments (Cloudflare Worker secrets, CI/CD) with the new keys.
  4. Revoke or rotate the exposed private key if it was in use anywhere.
  5. Verify there are no remaining references to the old key:
#!/bin/bash
# Search all files (including docs) for the leaked key
rg -F "j0XcVju6mRn94xN82R4Ti7FowCPj5V9ipdnnorCbvAQ"
🤖 Prompt for AI Agents
In worker/VAPID_KEYS.md between lines 15 and 28, the documentation currently
exposes a real VAPID private key, which is a critical security risk. Replace the
actual private key string with a placeholder like YOUR_ACTUAL_PRIVATE_KEY_HERE
to avoid exposing sensitive information. After this, generate new VAPID keys
using the generate-vapid-keys.sh script, update all environments with the new
keys, and revoke the exposed key if it was in use. Finally, ensure no other
references to the old key remain in the codebase or documentation.

Comment on lines +92 to +108
cat > nostr-notification-bot.service << EOF
[Unit]
Description=Nostr Groups Notification Bot
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$(pwd)
ExecStart=/usr/bin/node dist/notification-bot.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security issue: systemd service runs as current user.

The systemd service is configured to run as the current user ($USER), which may not be appropriate for a production service and could pose security risks.

 [Service]
 Type=simple
-User=$USER
+User=nostr-bot
+Group=nostr-bot
 WorkingDirectory=$(pwd)
 ExecStart=/usr/bin/node dist/notification-bot.js
 Restart=always
 RestartSec=10
 Environment=NODE_ENV=production
+
+# Security hardening
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=strict
+ProtectHome=true
+ReadWritePaths=/var/lib/nostr-bot

Additionally, add instructions to create a dedicated user:

# Before installing the service, create a dedicated user
sudo useradd -r -s /bin/false nostr-bot
sudo mkdir -p /var/lib/nostr-bot
sudo chown nostr-bot:nostr-bot /var/lib/nostr-bot
🤖 Prompt for AI Agents
In deploy-bot.sh around lines 92 to 108, the systemd service runs as the current
user ($USER), which is a security risk for production. Fix this by creating a
dedicated system user (e.g., nostr-bot) with no login shell before installing
the service, setting appropriate ownership for its working directory (e.g.,
/var/lib/nostr-bot), and then update the service file to run as this dedicated
user instead of $USER.

Comment on lines +32 to +33
cp bot-package.json "$BOT_DIR/package.json"
cp -r src/bot/* "$BOT_DIR/"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for file copy operations.

The script copies files without checking if source files exist, which could lead to runtime errors.

 # Copy bot files
 echo "📄 Copying bot files..."
-cp bot-package.json "$BOT_DIR/package.json"
-cp -r src/bot/* "$BOT_DIR/"
+if [ ! -f "bot-package.json" ]; then
+    echo "❌ bot-package.json not found"
+    exit 1
+fi
+
+if [ ! -d "src/bot" ]; then
+    echo "❌ src/bot directory not found"
+    exit 1
+fi
+
+cp bot-package.json "$BOT_DIR/package.json"
+cp -r src/bot/* "$BOT_DIR/"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cp bot-package.json "$BOT_DIR/package.json"
cp -r src/bot/* "$BOT_DIR/"
# Copy bot files
echo "📄 Copying bot files..."
if [ ! -f "bot-package.json" ]; then
echo "❌ bot-package.json not found"
exit 1
fi
if [ ! -d "src/bot" ]; then
echo "❌ src/bot directory not found"
exit 1
fi
cp bot-package.json "$BOT_DIR/package.json"
cp -r src/bot/* "$BOT_DIR/"
🤖 Prompt for AI Agents
In deploy-bot.sh around lines 32 to 33, the script copies files without
verifying if the source files exist, which can cause runtime errors. Add checks
before each cp command to confirm the source files or directories exist. If they
do not exist, output an error message and exit the script to prevent further
execution. This ensures safer file operations and clearer error handling.

Comment on lines +151 to +160

const subscriptions = await this.env.KV.list({ prefix: 'sub:' });
const users: Record<string, string[]> = {};

for (const key of subscriptions.keys) {
const sub = await this.env.KV.get(key.name, 'json') as UserSubscription;
if (sub) {
users[sub.npub] = sub.groups;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

KV-wide scan can become expensive – add pagination / prefix narrowing
handleGetSubscriptions() calls env.KV.list({ prefix: 'sub:' }) and then performs an unbounded loop with individual get calls.
With thousands of subscribers this will exceed the 10 ms CPU / 1 k read limit for Workers and may return truncated results.

Consider:

  1. Using list({ prefix, limit: 1000, cursor }) in a loop and streaming JSON back to the client.
  2. Caching the result in KV with a short TTL if the endpoint is for dashboards only.
🤖 Prompt for AI Agents
In worker/cloudflare-worker/src/worker-api-enhanced.ts around lines 151 to 160,
the current code uses env.KV.list with a prefix but fetches all keys at once and
then performs individual get calls, which can exceed Cloudflare Workers' CPU and
read limits with many subscribers. To fix this, implement pagination by calling
list with prefix, limit (e.g., 1000), and cursor in a loop to fetch keys in
batches, processing each batch before continuing. Additionally, consider
streaming the JSON response incrementally to the client to avoid memory issues
and optionally cache the aggregated result in KV with a short TTL if the
endpoint is for dashboard use only.

Comment on lines 99 to 116
private async handleSubscribe(request: Request): Promise<Response> {
const body = await request.json() as any;

const userSub: UserSubscription = {
npub: body.npub,
subscription: body.subscription,
groups: body.preferences?.subscriptions?.groups || [],
preferences: {
mentions: body.preferences?.settings?.mentions ?? true,
groupActivity: body.preferences?.settings?.groupActivity ?? true,
reactions: body.preferences?.settings?.reactions ?? false,
moderation: body.preferences?.settings?.moderation ?? true,
frequency: body.preferences?.settings?.frequency || 'immediate',
},
createdAt: Date.now(),
lastNotified: 0,
};

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Group source path likely wrong – may silently drop user’s group selections
handleSubscribe() fetches groups from body.preferences?.subscriptions?.groups, yet the payload structure sent by the frontend (see other workers in the repo) places groups at the same level as preferences.settings.
If the caller follows the earlier contract ({ npub, subscription, groups: [...] }) this expression resolves to undefined, resulting in an empty array and losing the user’s intended group subscriptions.

-  groups: body.preferences?.subscriptions?.groups || [],
+  groups: Array.isArray(body.groups)
+    ? body.groups
+    : body.preferences?.subscriptions?.groups || [],

Please align the schema with the existing API or update the frontend + documentation accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async handleSubscribe(request: Request): Promise<Response> {
const body = await request.json() as any;
const userSub: UserSubscription = {
npub: body.npub,
subscription: body.subscription,
groups: body.preferences?.subscriptions?.groups || [],
preferences: {
mentions: body.preferences?.settings?.mentions ?? true,
groupActivity: body.preferences?.settings?.groupActivity ?? true,
reactions: body.preferences?.settings?.reactions ?? false,
moderation: body.preferences?.settings?.moderation ?? true,
frequency: body.preferences?.settings?.frequency || 'immediate',
},
createdAt: Date.now(),
lastNotified: 0,
};
private async handleSubscribe(request: Request): Promise<Response> {
const body = await request.json() as any;
const userSub: UserSubscription = {
npub: body.npub,
subscription: body.subscription,
groups: Array.isArray(body.groups)
? body.groups
: body.preferences?.subscriptions?.groups || [],
preferences: {
mentions: body.preferences?.settings?.mentions ?? true,
groupActivity: body.preferences?.settings?.groupActivity ?? true,
reactions: body.preferences?.settings?.reactions ?? false,
moderation: body.preferences?.settings?.moderation ?? true,
frequency: body.preferences?.settings?.frequency || 'immediate',
},
createdAt: Date.now(),
lastNotified: 0,
};
🤖 Prompt for AI Agents
In worker/cloudflare-worker/src/worker-api-enhanced.ts around lines 99 to 116,
the code incorrectly accesses user group subscriptions from
body.preferences?.subscriptions?.groups, but the frontend payload places groups
at the same level as preferences.settings or directly under body. Update the
code to fetch groups from the correct path, such as body.groups or
body.preferences.groups, matching the existing API schema to avoid losing user
group selections. Verify and align this with the frontend payload structure and
update documentation if necessary.

Comment on lines 333 to 363
for (const notification of payload.notifications) {
const sub = await env.KV.get(`sub:${notification.npub}`, 'json') as UserSubscription;
if (!sub) continue;

// Check preferences
if (!this.shouldSendNotification(sub, notification.type)) {
console.log(`Skipping ${notification.type} for ${notification.npub} (preferences)`);
continue;
}

// Rate limiting
if (sub.lastNotified && Date.now() - sub.lastNotified < 60000) { // 1 minute
console.log(`Rate limiting ${notification.npub}`);
continue;
}

// Build notification
const pushPayload = await this.buildPushPayload(payload.event, notification, sub);

// Send push notification
const success = await this.sendPushNotification(env, sub.endpoint, sub.keys, pushPayload);

if (success) {
sent++;
// Update last notified
sub.lastNotified = Date.now();
await env.KV.put(`sub:${notification.npub}`, JSON.stringify(sub));
} else {
failed++;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

High-cardinality KV writes inside synchronous loop – move to ctx.waitUntil
Each successful push performs await env.KV.put(...) to update lastNotified.
For large batches (e.g., broadcast to thousands) this turns the worker into a serial KV writer and may exceed request time limits.

Pattern:

// inside the loop
ctx.waitUntil(
  env.KV.put(`sub:${notification.npub}`, JSON.stringify({ ...sub, lastNotified: Date.now() }))
);

Return immediately once sending is done to keep latency low.

🤖 Prompt for AI Agents
In worker/cloudflare-worker/src/push-notification-worker.ts lines 333 to 363,
the code performs await env.KV.put(...) inside a synchronous loop for each
successful notification, causing serial KV writes that can exceed request time
limits for large batches. To fix this, remove the await from env.KV.put and
instead call ctx.waitUntil(env.KV.put(...)) to perform the KV writes
asynchronously in the background. Also, ensure the loop returns immediately
after sending notifications to keep latency low.

Comment on lines 447 to 462
async sendPushNotification(env: Env, endpoint: string, keys: any, payload: any): Promise<boolean> {
try {
// For now, use a simple approach that works with FCM
// In production, you'd want to use proper web-push encryption

const message = JSON.stringify(payload);

const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `vapid t=${env.VAPID_PUBLIC_KEY}, k=${env.VAPID_PRIVATE_KEY}`,
'TTL': '86400',
'Urgency': payload.data?.priority === 'high' ? 'high' : 'normal'
},
body: message
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Web-Push protocol not respected – pushes will fail on all browsers
sendPushNotification() POSTs the raw payload to the endpoint with pseudo-VAPID headers.
The Web-Push standard (RFC 8292) requires:

  1. Payload to be encrypted with the user’s public key (p256dh) and the server’s private key.
  2. Authorization: WebPush <JWT> header (NOT vapid t=…, k=…).
  3. Correct Content-Encoding: aes128gcm / aesgcm header.

Without encryption, most push services reply 400/401 and the message is discarded.

Adopt a proven library (e.g. @web-push-libs/web-push – compatible with Workers after bundling) or port its encryption logic.
Failing silently here means notifications never reach end users.

🤖 Prompt for AI Agents
In worker/cloudflare-worker/src/push-notification-worker.ts around lines 447 to
462, the sendPushNotification function incorrectly sends the raw payload with
invalid VAPID headers, violating the Web-Push protocol. To fix this, replace the
manual fetch call with a call to a proven web-push library like
@web-push-libs/web-push that handles payload encryption using the user's public
key and server's private key, sets the correct Authorization header with a
WebPush JWT, and includes the proper Content-Encoding header. Ensure errors are
caught and logged to avoid silent failures.

Comment on lines +6 to +8
import { NostrEvent, verifyEvent } from 'nostr-tools';
import { NotificationSystem, NOTIFICATION_EVENT_KINDS } from './notification-system';
import { WorkerAPI } from './worker-api';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong import path – module likely renamed
The file imports WorkerAPI from './worker-api', but the new implementation lives in worker-api-enhanced.ts.
At build time this will resolve to the older, non-durable version (or fail if absent).

-import { WorkerAPI } from './worker-api';
+import { WorkerAPI } from './worker-api-enhanced';

Update the path or add an explicit barrel export.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { NostrEvent, verifyEvent } from 'nostr-tools';
import { NotificationSystem, NOTIFICATION_EVENT_KINDS } from './notification-system';
import { WorkerAPI } from './worker-api';
import { NostrEvent, verifyEvent } from 'nostr-tools';
import { NotificationSystem, NOTIFICATION_EVENT_KINDS } from './notification-system';
-import { WorkerAPI } from './worker-api';
+import { WorkerAPI } from './worker-api-enhanced';
🤖 Prompt for AI Agents
In worker/cloudflare-worker/src/worker-enhanced.ts around lines 6 to 8, the
import path for WorkerAPI is incorrect as it points to './worker-api' instead of
the updated './worker-api-enhanced.ts'. Update the import statement to import
WorkerAPI from './worker-api-enhanced' to ensure the correct, durable version is
used.

@rabble
Copy link
Contributor Author

rabble commented May 23, 2025

Hi @rabble and team,

I noticed that the auto-generated CodeRabbit summary in this PR seems to be describing features from a different set of changes (notifications page enhancements, push notification system, etc.) rather than the relay crawler worker and its associated workflows in PR #318. To keep the release notes accurate, here are the discrepancies:

  • New Features: Mentions an enhanced notifications page and push notification hooks, which aren’t part of this PR.
  • Bug Fixes: References dialog management improvements not introduced here.
  • Documentation & Chores: Lists guides and environment updates that belong to different changes.

Could we regenerate or update the CodeRabbit summary so it accurately reflects the relay crawler worker implementation and GitHub Actions workflows? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants