Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removing enable api testing #338

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from

Conversation

rohitniyerclevertap
Copy link
Contributor

@rohitniyerclevertap rohitniyerclevertap commented Jan 21, 2025

Changes

We had created notifications.enable api to call the new soft prompt, we have reverted back to the old api and made changes in notifications.push api itself.
We did this so that end users don't have to make changes to their code to call new and old api.
We have handled the race condition as well when user calls notification.push and when the sdk hasn't yet received the web push config from the backend

Changes to Public Facing API if any

Removed notifications.enable api
Made changes to notifications.push api
Added checks to see if webpushConfig is received from the backend and based on the web push config we render wither old or the new soft prompt

Please list the impact on the public facing API if any
Notification.enable is no longer supported. It was the new api which we had created to render new soft prompt. Since we removed the new api, rendering of new soft prompt is taken care by .push api.

How Has This Been Tested?

Describe the testing approach and any relevant configurations (e.g., environment, platform)
Tested on CtShoptesting by overriding clevertap.min.js
Tested on chetan/noc-testing website again by overriding clevertap.js

Checklist

  • Code compiles without errors
  • Version Bump added to package.json & CHANGELOG.md
  • All tests pass
  • Build process is successful
  • Documentation has been updated (if needed)

Summary by CodeRabbit

  • New Features

    • Enhanced web push notification handling with improved configuration management.
    • Added support for soft prompt processing during notification setup.
    • Introduced more flexible notification configuration options.
  • Bug Fixes

    • Improved error handling for web push notification subscriptions.
    • Added robust checks for notification configuration states.
  • Refactor

    • Restructured notification handler methods for better modularity.
    • Updated notification setup process to be more dynamic and configurable.

Copy link

coderabbitai bot commented Jan 21, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request introduces significant modifications to the web push notification system across multiple files. The changes focus on enhancing the NotificationHandler class and related functions, improving the handling of web push configurations, soft prompts, and browser-specific notification setups. The modifications include adding new methods like setupWebPush, processSoftPrompt, and setNotificationHandlerValues, refactoring existing methods, and improving the overall logic for managing push notification interactions.

Changes

File Change Summary
clevertap.js - Added setupWebPush method
- Modified enable and push methods
- Added parseDisplayArgs and processSoftPrompt functions
- Added setNotificationHandlerValues function
src/modules/notification.js - Replaced push method with setupWebPush
- Added setNotificationHandlerValues and processSoftPrompt methods
- Enhanced error handling and subscription process
src/modules/webPushPrompt/prompt.js - Added setNotificationHandlerValues function
- Modified processWebPushConfig for error handling
- Added processSoftPrompt function
- Updated parseDisplayArgs logic

Possibly related PRs

  • [WEB-3309] Safari Web Push #340: This PR focuses on implementing Safari VAPID migration and enhancing the notification subscription flow for Safari web push notifications, which is directly related to the changes made in the main PR regarding web push notifications and the handling of configurations.
  • Web 3309 patch - Fixed hardcoded feature identification with API check based logic  #345: This PR addresses compatibility issues for Web Push notifications across different browsers, including enhancements to the notification handling logic, which aligns with the modifications made in the main PR to improve web push notification functionality.

Suggested reviewers

  • kkyusuftk
  • ThisIsRaghavGupta

Poem

🐰 A Rabbit's Ode to Push Notifications 🔔

Soft prompts dancing, config in flight,
Browsers listening with all their might,
Notifications leap from code to screen,
A push so clever, it's almost unseen!
Web magic brewing, notifications shine bright! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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. (Beta)
  • @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.

@rohitniyerclevertap rohitniyerclevertap marked this pull request as ready for review January 28, 2025 08:51
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: 1

🧹 Nitpick comments (9)
src/modules/notification.js (5)

38-50: Review the logical flow of push method.

The push(...) method reads the webPushConfigResponseReceived flag and either calls processSoftPrompt or saves a notificationPushCalled flag. This appears consistent with the new approach. Consider adding a short comment explaining why these flags are used, for easier maintainability and onboarding.


150-152: Suggested null-check simplification.

Variables softPromptCard and oldSoftPromptCard may be null if elements are missing in the DOM, but are guarded later with if statements. This is fine, though you may consider consolidating these references in a helper function for clarity.


179-184: Consider consolidating DOM removal logic.

Removing multiple DOM elements in separate blocks is clear, but you could unify these removals into a small helper to reduce repetition and keep the flow concise.


187-187: Use logger for consistency.

Replace console.log('rejected subscription') with the logger to maintain consistent logging practices.

- console.log('rejected subscription')
+ this.#logger.error('Subscription was rejected')

203-208: Refactor repeated removal logic.

Lines 203-208 re-implement the same pattern for removing softPromptCard and oldSoftPromptCard. Consider a helper method (e.g. #removePromptElements()) to reduce duplication:

+ #removePromptElements(element1, element2) {
+   if (element1) element1.parentNode.removeChild(element1)
+   if (element2) element2.parentNode.removeChild(element2)
+ }

...
- if (softPromptCard) {
-   softPromptCard.parentNode.removeChild(softPromptCard)
- }
- if (oldSoftPromptCard) {
-   oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
- }
+ this.#removePromptElements(softPromptCard, oldSoftPromptCard)
src/modules/webPushPrompt/prompt.js (4)

11-14: Consider encapsulating global state

These module-level variables introduce mutable global state which can make the code harder to maintain and test. Consider encapsulating them within a class or configuration object.

-let logger = null
-let account = null
-let request = null
-let displayArgs = null
+class NotificationConfig {
+  constructor() {
+    this.logger = null;
+    this.account = null;
+    this.request = null;
+    this.displayArgs = null;
+  }
+  
+  setValues(values = {}) {
+    Object.assign(this, values);
+  }
+}
+
+const config = new NotificationConfig();

16-21: Add input validation

The function accepts values without any type checking or validation. Consider adding validation to ensure the required properties have appropriate types and values.

 export const setNotificationHandlerValues = (notificationValues = {}) => {
+  if (!isObject(notificationValues)) {
+    throw new TypeError('notificationValues must be an object');
+  }
+
   logger = notificationValues.logger
   account = notificationValues.account
   request = notificationValues.request
   displayArgs = notificationValues.displayArgs
 }

64-70: Add input validation and JSDoc documentation

The function handles different argument formats without clear documentation or validation.

+/**
+ * Parses display arguments for web push setup
+ * @param {Array|Object} displayArgs - Display arguments in legacy array format or new object format
+ * @returns {{serviceWorkerPath: string|undefined, skipDialog: boolean|undefined}}
+ * @throws {TypeError} If displayArgs is neither an array nor an object
+ */
 export const parseDisplayArgs = (displayArgs) => {
+  if (!Array.isArray(displayArgs) && !isObject(displayArgs)) {
+    throw new TypeError('displayArgs must be an array or object');
+  }
+
   if (displayArgs.length === 1 && isObject(displayArgs[0])) {
     const { serviceWorkerPath, skipDialog } = displayArgs[0]
+    if (serviceWorkerPath && typeof serviceWorkerPath !== 'string') {
+      throw new TypeError('serviceWorkerPath must be a string');
+    }
+    if (skipDialog !== undefined && typeof skipDialog !== 'boolean') {
+      throw new TypeError('skipDialog must be a boolean');
+    }
     return { serviceWorkerPath, skipDialog }
   }

   return { serviceWorkerPath: undefined, skipDialog: displayArgs[5] }
 }

83-85: Add parameter documentation

The skipDialog parameter's purpose and impact should be documented.

+/**
+ * @param {boolean|null} skipDialog - When true, bypasses the notification dialog and directly enables push notifications.
+ *                                   When null, defaults to false.
+ */
   if (skipDialog === null) {
     skipDialog = false
   }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 013900e and 3a01657.

⛔ Files ignored due to path filters (2)
  • clevertap.js.map is excluded by !**/*.map
  • clevertap.min.js is excluded by !**/*.min.js
📒 Files selected for processing (3)
  • clevertap.js (7 hunks)
  • src/modules/notification.js (5 hunks)
  • src/modules/webPushPrompt/prompt.js (3 hunks)
🔇 Additional comments (14)
src/modules/notification.js (2)

9-9: Import path check.

The import statement looks correct, and the functions setNotificationHandlerValues and processSoftPrompt appear to be used consistently in this file.


34-34: Method naming consistency.

setupWebPush is descriptive and aligns well with the internal #setUpWebPush method. This explicit naming helps clarify the setup process for web push notifications.

clevertap.js (11)

5304-5306: LGTM: New method added to handle web push setup

The new setupWebPush method is added to handle web push notification setup by calling the existing _setUpWebPush method with display arguments.


5315-5320: LGTM: Proper initialization of notification handler values

The code correctly initializes notification handler values by setting logger, account, request and display arguments.


Line range hint 5446-5482: Cleanup of notification UI elements

The code properly cleans up notification UI elements after successful subscription:

  1. Removes existing bell wrapper if present
  2. Removes soft prompt card if present
  3. Removes old soft prompt card if present

Line range hint 5484-5510: Proper error handling for subscription failures

The code handles subscription failures appropriately by:

  1. Unsubscribing from web push if error occurs
  2. Logging error message
  3. Cleaning up UI elements
  4. Retrying subscription with skipDialog flag

5767-5777: LGTM: Notification handler values setter

The setNotificationHandlerValues function properly sets all required values for notification handling.


5833-5848: LGTM: Display arguments parser

The parseDisplayArgs function properly extracts service worker path and skip dialog options from display arguments.


5864-5866: LGTM: Default skip dialog handling

The code properly handles the case when skipDialog is null by defaulting it to false.


5309-5310: Verify web push configuration check

The code checks for web push configuration presence in local storage before proceeding with push notification setup.

✅ Verification successful

Web push configuration check is properly implemented

The code correctly checks for web push configuration presence using localStorage/cookie storage before proceeding with push notification setup. The implementation includes proper configuration processing and state management through the webPushConfigResponseReceived flag.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if web push config is properly stored in localStorage
ast-grep --pattern 'StorageManager.readFromLSorCookie("webPushConfigResponseReceived")'

Length of output: 89


Script:

#!/bin/bash
# Search for webPushConfig related code
rg "webPushConfig" -A 2 -B 2

# Try ast-grep with single quotes
ast-grep --pattern "StorageManager.readFromLSorCookie('webPushConfigResponseReceived')"

# Look for StorageManager write operations for web push
rg "StorageManager\.(write|save).*webPushConfig" -A 2 -B 2

Length of output: 68081


5799-5832: Verify soft prompt processing logic

The processSoftPrompt function:

  1. Gets web push config from storage
  2. Creates notification handler
  3. Handles different scenarios based on config values
  4. Sets up web push appropriately
✅ Verification successful

Soft prompt processing implementation verified

The implementation correctly handles all scenarios:

  • Empty configuration
  • New box type with bell icon
  • Old box type with direct setup
  • Preview mode cases
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check soft prompt processing implementation
ast-grep --pattern 'function processSoftPrompt() {
  const webPushConfig = StorageManager.readFromLSorCookie(WEBPUSH_CONFIG) || {};
  $$$
}'

Length of output: 142


Script:

#!/bin/bash
# Check soft prompt as arrow function
ast-grep --pattern 'const processSoftPrompt = () => {
  const webPushConfig = StorageManager.readFromLSorCookie(WEBPUSH_CONFIG) || {};
  $$$
}'

# Find StorageManager implementation
ast-grep --pattern 'class StorageManager {
  $$$
  readFromLSorCookie($$$) {
    $$$
  }
  $$$
}'

# Find NotificationHandler class
ast-grep --pattern 'class NotificationHandler {
  $$$
}'

# Find enablePush function
ast-grep --pattern 'const enablePush = ($$$) => {
  $$$
}'

Length of output: 30815


5322-5326: Verify soft prompt processing logic

The code handles two scenarios:

  1. If web push config is present - processes soft prompt immediately
  2. If not present - saves notification push call flag to localStorage for later processing
✅ Verification successful

Web push configuration and soft prompt handling verified

The code correctly implements a two-path logic for handling web push notifications:

  1. With web push config present - Immediately processes the soft prompt via NotificationHandler
  2. Without config - Saves notification state for later processing when config becomes available
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if soft prompt processing is properly handled
ast-grep --pattern 'if (isWebPushConfigPresent) {
  processSoftPrompt();
} else {
  StorageManager.saveToLSorCookie("notificationPushCalled", true);
}'

Length of output: 153


Script:

#!/bin/bash
# Search for processSoftPrompt function and notificationPushCalled usages
echo "=== processSoftPrompt function ==="
rg "processSoftPrompt" -A 5

echo -e "\n=== notificationPushCalled usages ==="
rg "notificationPushCalled"

echo -e "\n=== Web push config related code ==="
rg "isWebPushConfigPresent"

Length of output: 66623


Script:

#!/bin/bash
# Search for WEBPUSH_CONFIG constant and related code
echo "=== WEBPUSH_CONFIG constant ==="
rg "WEBPUSH_CONFIG" -B 2 -A 2

echo -e "\n=== processSoftPrompt implementation ==="
rg "processSoftPrompt = " -A 10

Length of output: 67399


5791-5797: Verify web push config response handling

The code saves web push config response flag and processes soft prompt if notification was previously called.

✅ Verification successful

Web push config response handling is correctly implemented

The code follows a proper sequence of saving the web push config response flag and processing soft prompt only when notification was previously called. The implementation includes appropriate state management and handles different notification display types safely.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check web push config response handling
ast-grep --pattern 'StorageManager.saveToLSorCookie("webPushConfigResponseReceived", true);
const isNotificationPushCalled = StorageManager.readFromLSorCookie("notificationPushCalled");
if (isNotificationPushCalled) {
  processSoftPrompt();
}'

Length of output: 245


Script:

#!/bin/bash
# Check StorageManager usage and related functions with more flexible patterns
echo "=== StorageManager usage ==="
ast-grep --pattern 'StorageManager.saveToLSorCookie($_)'

echo -e "\n=== processSoftPrompt implementation ==="
ast-grep --pattern 'function processSoftPrompt() {
  $$$
}'

echo -e "\n=== Notification push handling ==="
rg "notificationPushCalled" -A 3

Length of output: 66498


Script:

#!/bin/bash
echo "=== webPushConfigResponseReceived usage ==="
rg "webPushConfigResponseReceived" -A 2

echo -e "\n=== processSoftPrompt implementation ==="
rg "function processSoftPrompt" -A 10

echo -e "\n=== Notification push handling ==="
rg "notificationPushCalled" -B 2 -A 2

Length of output: 67099


Script:

#!/bin/bash
# Search for processSoftPrompt with more context
echo "=== processSoftPrompt definition ==="
rg -B 2 -A 15 "processSoftPrompt"

echo -e "\n=== Web push config and notification flow ==="
rg "webPushConfigResponseReceived|notificationPushCalled|processSoftPrompt" -B 2 -A 2

Length of output: 71249

src/modules/webPushPrompt/prompt.js (1)

44-62: 🛠️ Refactor suggestion

Refactor for better separation of concerns

The function handles multiple responsibilities (config reading, notification setup, prompt type handling). Consider breaking it down into smaller, focused functions.

 export const processSoftPrompt = () => {
+  const config = getWebPushConfig()
+  if (!isValidConfig(config)) {
+    return setupDefaultWebPush()
+  }
+  
+  handlePromptType(config)
+}
+
+const getWebPushConfig = () => {
   const webPushConfig = StorageManager.readFromLSorCookie(WEBPUSH_CONFIG) || {}
+  return webPushConfig
+}
+
+const isValidConfig = (config) => {
+  return Object.keys(config).length > 0
+}
+
+const setupDefaultWebPush = () => {
   const notificationHandler = new NotificationHandler({ logger, session: {}, request, account })
-  if (!(Object.keys(webPushConfig).length > 0)) {
-    notificationHandler.setApplicationServerKey(appServerKey)
-    notificationHandler.setupWebPush(displayArgs)
-    return 0
-  }
+  notificationHandler.setApplicationServerKey(appServerKey)
+  notificationHandler.setupWebPush(displayArgs)
+}
+
+const handlePromptType = (config) => {
+  const { showBox, showBellIcon, boxType } = config
   const { serviceWorkerPath, skipDialog } = parseDisplayArgs(displayArgs)
   if (showBellIcon || (showBox && boxType === 'new')) {
     enablePush(logger, account, request, serviceWorkerPath, skipDialog)
   }

   if (showBox && boxType === 'old') {
     notificationHandler.setApplicationServerKey(appServerKey)
     notificationHandler.setupWebPush(displayArgs)
   }
 }

Remove magic number

The function returns 0 without explanation. Consider using a named constant or removing the return value if it's not used.

-    return 0
+    return // Remove if return value isn't used
// OR
+const SETUP_SUCCESS = 0
+    return SETUP_SUCCESS

Add null checks for dependencies

The function creates a NotificationHandler instance without validating its dependencies.

+  if (!logger || !request || !account) {
+    throw new Error('Missing required notification handler dependencies')
+  }
   const notificationHandler = new NotificationHandler({ logger, session: {}, request, account })

Comment on lines 36 to 41
StorageManager.saveToLSorCookie('webPushConfigResponseReceived', true)
const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
if (isNotificationPushCalled) {
processSoftPrompt()
}
}
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 storage operations

Storage operations could fail (e.g., in private browsing mode). Consider adding try-catch blocks and fallback behavior.

-    StorageManager.saveToLSorCookie('webPushConfigResponseReceived', true)
-    const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
-    if (isNotificationPushCalled) {
-      processSoftPrompt()
-    }
+    try {
+      StorageManager.saveToLSorCookie('webPushConfigResponseReceived', true)
+      const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
+      if (isNotificationPushCalled) {
+        processSoftPrompt()
+      }
+    } catch (error) {
+      logger?.error('Failed to process web push config:', error)
+      // Fallback: Attempt to process soft prompt anyway
+      processSoftPrompt()
+    }
📝 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
StorageManager.saveToLSorCookie('webPushConfigResponseReceived', true)
const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
if (isNotificationPushCalled) {
processSoftPrompt()
}
}
try {
StorageManager.saveToLSorCookie('webPushConfigResponseReceived', true)
const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
if (isNotificationPushCalled) {
processSoftPrompt()
}
} catch (error) {
logger?.error('Failed to process web push config:', error)
// Fallback: Attempt to process soft prompt anyway
processSoftPrompt()
}
}

}).catch((error) => {
// unsubscribe from webpush if error
console.log('rejected subscription')
Copy link
Contributor

Choose a reason for hiding this comment

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

use logger

if (!(Object.keys(webPushConfig).length > 0)) {
notificationHandler.setApplicationServerKey(appServerKey)
notificationHandler.setupWebPush(displayArgs)
return 0
Copy link
Contributor

@ThisIsRaghavGupta ThisIsRaghavGupta Jan 28, 2025

Choose a reason for hiding this comment

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

Nit: return

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: 0

🧹 Nitpick comments (6)
src/modules/webPushPrompt/prompt.js (3)

11-21: Consider refactoring global variables into a class.

Global variables can make testing difficult and lead to maintainability issues. Consider encapsulating these variables and the setNotificationHandlerValues function within a class.

-let logger = null
-let account = null
-let request = null
-let displayArgs = null
-
-export const setNotificationHandlerValues = (notificationValues = {}) => {
-  logger = notificationValues.logger
-  account = notificationValues.account
-  request = notificationValues.request
-  displayArgs = notificationValues.displayArgs
-}
+class NotificationPromptHandler {
+  #logger = null;
+  #account = null;
+  #request = null;
+  #displayArgs = null;
+
+  setValues(notificationValues = {}) {
+    this.#logger = notificationValues.logger;
+    this.#account = notificationValues.account;
+    this.#request = notificationValues.request;
+    this.#displayArgs = notificationValues.displayArgs;
+  }
+}

50-67: Add error handling for storage operations.

Storage operations could fail (e.g., in private browsing mode). Consider adding try-catch blocks.

 export const processSoftPrompt = () => {
+  try {
     const webPushConfig = StorageManager.readFromLSorCookie(WEBPUSH_CONFIG) || {}
     const notificationHandler = new NotificationHandler({ logger, session: {}, request, account })
     if (!(Object.keys(webPushConfig).length > 0)) {
       notificationHandler.setApplicationServerKey(appServerKey)
       notificationHandler.setupWebPush(displayArgs)
       return
     }
     const { showBox, showBellIcon, boxType } = webPushConfig
     const { serviceWorkerPath, skipDialog } = parseDisplayArgs(displayArgs)
     if (showBellIcon || (showBox && boxType === 'new')) {
       enablePush(logger, account, request, serviceWorkerPath, skipDialog)
     }
     if (showBox && boxType === 'old') {
       notificationHandler.setApplicationServerKey(appServerKey)
       notificationHandler.setupWebPush(displayArgs)
     }
+  } catch (error) {
+    logger?.error('Failed to process soft prompt:', error)
+  }
 }

70-77: Add input validation for required fields.

Consider validating the required fields in the display arguments to prevent runtime errors.

 export const parseDisplayArgs = (displayArgs) => {
+  if (!displayArgs || !Array.isArray(displayArgs)) {
+    return { serviceWorkerPath: undefined, skipDialog: false }
+  }
+
   if (displayArgs.length === 1 && isObject(displayArgs[0])) {
     const { serviceWorkerPath, skipDialog } = displayArgs[0]
+    if (serviceWorkerPath && typeof serviceWorkerPath !== 'string') {
+      logger?.warn('Invalid serviceWorkerPath type')
+      return { serviceWorkerPath: undefined, skipDialog: false }
+    }
     return { serviceWorkerPath, skipDialog }
   }
 
   return { serviceWorkerPath: undefined, skipDialog: displayArgs[5] }
 }
src/modules/notification.js (3)

38-59: Add error handling for storage operations in push method.

Storage operations could fail in private browsing mode. Consider adding try-catch blocks.

   push (...displayArgs) {
-    const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
-    setNotificationHandlerValues({
-      logger: this.#logger,
-      account: this.#account,
-      request: this.#request,
-      displayArgs
-    })
-    if (isWebPushConfigPresent) {
-      processSoftPrompt()
-    } else {
-      StorageManager.saveToLSorCookie('notificationPushCalled', true)
+    try {
+      const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
+      setNotificationHandlerValues({
+        logger: this.#logger,
+        account: this.#account,
+        request: this.#request,
+        displayArgs
+      })
+      if (isWebPushConfigPresent) {
+        processSoftPrompt()
+      } else {
+        StorageManager.saveToLSorCookie('notificationPushCalled', true)
+      }
+    } catch (error) {
+      this.#logger?.error('Failed to process push notification:', error)
     }
   }

Line range hint 159-193: Refactor duplicate DOM cleanup logic.

Consider extracting the repeated DOM cleanup logic into a helper function.

+    const cleanupElement = (elementId) => {
+      const element = document.getElementById(elementId)
+      if (element) {
+        element.parentNode.removeChild(element)
+      }
+    }
+
     serviceWorkerRegistration.pushManager.subscribe(subscribeObj)
       .then((subscription) => {
         // ... existing code ...
-        const existingBellWrapper = document.getElementById('bell_wrapper')
-        if (existingBellWrapper) {
-          existingBellWrapper.parentNode.removeChild(existingBellWrapper)
-        }
-        if (softPromptCard) {
-          softPromptCard.parentNode.removeChild(softPromptCard)
-        }
-        if (oldSoftPromptCard) {
-          oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
-        }
+        cleanupElement('bell_wrapper')
+        cleanupElement('pnWrapper')
+        cleanupElement('wzrk_wrapper')

211-216: Reuse the cleanup helper function in error handling.

The same DOM cleanup logic is duplicated. Use the suggested helper function here as well.

         }).catch((error) => {
           // ... existing code ...
-          if (softPromptCard) {
-            softPromptCard.parentNode.removeChild(softPromptCard)
-          }
-          if (oldSoftPromptCard) {
-            oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
-          }
+          cleanupElement('pnWrapper')
+          cleanupElement('wzrk_wrapper')
         })
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3a01657 and c35a46e.

📒 Files selected for processing (2)
  • src/modules/notification.js (5 hunks)
  • src/modules/webPushPrompt/prompt.js (3 hunks)
🔇 Additional comments (1)
src/modules/webPushPrompt/prompt.js (1)

36-47: LGTM! Good error handling implementation.

The addition of try-catch block with fallback behavior improves reliability when storage operations fail.

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: 2

🧹 Nitpick comments (4)
clevertap.js (1)

Line range hint 5336-6003: Architecture advice for web push notification flow

The current implementation could benefit from:

  1. Using a state machine pattern to manage the complex web push notification flow
  2. Implementing retry mechanisms with exponential backoff for failed web push registrations
  3. Adding telemetry/monitoring for web push success rates
  4. Considering a queue-based approach for handling race conditions

Would you like me to provide a detailed proposal for any of these improvements?

🧰 Tools
🪛 Biome (1.9.4)

[error] 5998-5998: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

src/modules/webPushPrompt/prompt.js (1)

12-17: Consider using a class to encapsulate global variables.

Using global variables can make the code harder to maintain and test. Consider encapsulating these variables within a class.

-let logger = null
-let account = null
-let request = null
-let displayArgs = null
-let fcmPublicKey = null
+class NotificationConfig {
+  constructor() {
+    this.logger = null
+    this.account = null
+    this.request = null
+    this.displayArgs = null
+    this.fcmPublicKey = null
+  }
+}
+const config = new NotificationConfig()
src/modules/notification.js (1)

Line range hint 228-264: Consider extracting DOM cleanup logic into a separate function.

The DOM cleanup logic is duplicated in both success and error cases. Consider extracting it into a reusable function.

+const cleanupSoftPromptElements = () => {
+  const softPromptCard = document.getElementById('pnWrapper')
+  const oldSoftPromptCard = document.getElementById('wzrk_wrapper')
+  const existingBellWrapper = document.getElementById('bell_wrapper')
+
+  if (existingBellWrapper) {
+    existingBellWrapper.parentNode.removeChild(existingBellWrapper)
+  }
+  if (softPromptCard) {
+    softPromptCard.parentNode.removeChild(softPromptCard)
+  }
+  if (oldSoftPromptCard) {
+    oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
+  }
+}

-            const existingBellWrapper = document.getElementById('bell_wrapper')
-            if (existingBellWrapper) {
-              existingBellWrapper.parentNode.removeChild(existingBellWrapper)
-            }
-            if (softPromptCard) {
-              softPromptCard.parentNode.removeChild(softPromptCard)
-            }
-            if (oldSoftPromptCard) {
-              oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
-            }
+            cleanupSoftPromptElements()

-            if (softPromptCard) {
-              softPromptCard.parentNode.removeChild(softPromptCard)
-            }
-            if (oldSoftPromptCard) {
-              oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
-            }
+            cleanupSoftPromptElements()

Also applies to: 282-287

src/clevertap.js (1)

551-557: Add error handling and improve state management.

While the logic for handling web push configuration is correct, consider these improvements for better robustness:

  1. Add try-catch blocks for storage operations
  2. Add null checks for storage read operations
  3. Consider using a more atomic approach to handle state changes

Here's a suggested implementation:

-      StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
-      this.notifications._enableWebPush(enabled, applicationServerKey)
-      const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
-      const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
-      if (isWebPushConfigPresent && isNotificationPushCalled) {
-        processSoftPrompt()
-      }
+      try {
+        StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
+        this.notifications._enableWebPush(enabled, applicationServerKey)
+        
+        const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
+        const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
+        
+        if (isWebPushConfigPresent && isNotificationPushCalled) {
+          processSoftPrompt()
+        }
+      } catch (error) {
+        this.#logger.error('Error in enableWebPush:', error)
+      }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c35a46e and 4922ba1.

⛔ Files ignored due to path filters (2)
  • clevertap.js.map is excluded by !**/*.map
  • clevertap.min.js is excluded by !**/*.min.js
📒 Files selected for processing (4)
  • clevertap.js (15 hunks)
  • src/clevertap.js (2 hunks)
  • src/modules/notification.js (5 hunks)
  • src/modules/webPushPrompt/prompt.js (10 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
clevertap.js

[error] 5998-5998: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (15)
clevertap.js (9)

5336-5338: LGTM: New setupWebPush method added

The new method provides a clean interface to set up web push notifications by delegating to the private _setUpWebPush method.


5341-5368: Improved web push notification handling with local storage flags

The push method has been enhanced to handle potential race conditions using two local storage flags:

  • webPushConfigResponseReceived: Tracks if backend webPushConfig is received
  • notificationPushCalled: Tracks if notifications.push was called before config

This ensures proper soft prompt rendering timing.


Line range hint 5559-5599: Cleanup of soft prompt elements on subscription

The code now properly cleans up both new and legacy soft prompt elements after successful subscription:

  • Removes the new soft prompt card (pnWrapper)
  • Removes the legacy soft prompt card (wzrk_wrapper)

This prevents stale UI elements from remaining in the DOM.


5620-5626: Consistent error handling for soft prompt cleanup

The code now handles cleanup of both new and legacy soft prompt elements even in error cases, ensuring a consistent user experience.


5881-5893: Improved notification handler initialization

The setNotificationHandlerValues function has been added to centralize the initialization of notification handler properties, making the code more maintainable.


5911-5922: Enhanced web push config processing with error handling

The code now includes proper error handling when processing web push config and checking for notification push calls:

try {
  StorageManager.saveToLSorCookie('webPushConfigResponseReceived', true);
  const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled');
  if (isNotificationPushCalled) {
    processSoftPrompt();
  }
} catch (error) {
  logger.error('Failed to process web push config:', error);
  // Fallback: Attempt to process soft prompt anyway
  processSoftPrompt();
}

5925-5975: Comprehensive soft prompt processing logic

The processSoftPrompt function has been added to handle:

  1. Reading web push config from storage
  2. Initializing notification handler
  3. Processing different box types (new/old)
  4. Handling bell icon display
  5. Managing notification push state

This centralization improves code organization and maintainability.


5976-6003: Utility function for parsing display arguments

The parseDisplayArgs function has been added to handle different argument formats consistently:

  • Single object with named parameters
  • Array of positional parameters

This improves code readability and maintainability.

🧰 Tools
🪛 Biome (1.9.4)

[error] 5998-5998: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


9153-9162: Enhanced web push enablement with application server key handling

The code now:

  1. Saves the application server key received flag
  2. Checks for both web push config and notification push call
  3. Processes soft prompt if both conditions are met

This ensures proper initialization sequence.

src/clevertap.js (3)

46-46: LGTM!

The import statement correctly adds the required functions from the web push prompt module.


46-46: LGTM!

The addition of processSoftPrompt import aligns with the PR objective of integrating soft prompt functionality into the existing API.


551-557: Verify removal of notifications.enable API.

Let's ensure there are no remaining references to the removed API in the codebase.

✅ Verification successful

Migration to notifications.push API is complete

The old notifications.enable API has been completely removed from the codebase, and the new implementation properly handles web push notifications using the flag-based system.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining usage of notifications.enable API
echo "Searching for notifications.enable usage..."
rg -l "notifications\.enable" || echo "No usage found"

# Search for potential migration issues
echo "Checking for potential migration patterns..."
rg -l "notifications\.push.*webPushConfig" || echo "No migration patterns found"

Length of output: 369


Script:

#!/bin/bash
# Check implementation in notification.js
echo "=== Checking notification.js implementation ==="
rg -A 5 "notifications\.push.*webPushConfig" src/modules/notification.js

echo -e "\n=== Checking clevertap.js implementation ==="
rg -A 5 "notifications\.push.*webPushConfig" clevertap.js

Length of output: 1267

src/modules/webPushPrompt/prompt.js (2)

77-90: LGTM!

The parseDisplayArgs function correctly handles both object and array arguments with proper defaults.


53-75: 🛠️ Refactor suggestion

Add error handling for storage operations in processSoftPrompt.

The function performs storage operations without error handling. Consider adding try-catch blocks.

 export const processSoftPrompt = () => {
+  try {
     const webPushConfig = StorageManager.readFromLSorCookie(WEBPUSH_CONFIG) || {}
     notificationHandler = new NotificationHandler({ logger, session: {}, request, account })
 
     if (!(Object.keys(webPushConfig).length > 0)) {
       notificationHandler.setApplicationServerKey(appServerKey)
       notificationHandler.setupWebPush(displayArgs)
       return
     }
     const { showBox, showBellIcon, boxType } = webPushConfig
 
     const { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback } = parseDisplayArgs(displayArgs)
     if (showBellIcon || (showBox && boxType === 'new')) {
       const enablePushParams = { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback, logger, request, account, fcmPublicKey }
       enablePush(enablePushParams)
     }
     if (showBox && boxType === 'old') {
       notificationHandler.setApplicationServerKey(appServerKey)
       notificationHandler.setupWebPush(displayArgs)
     }
     StorageManager.saveToLSorCookie('notificationPushCalled', false)
     StorageManager.saveToLSorCookie('applicationServerKeyReceived', false)
+  } catch (error) {
+    logger?.error('Failed to process soft prompt:', error)
+  }
 }

Likely invalid or redundant comment.

src/modules/notification.js (1)

42-65: LGTM!

The push method implementation is well-documented and handles the race condition between web push config and notification push calls effectively.

clevertap.js Outdated
Comment on lines 5997 to 5998
serviceWorkerPath: undefined,
skipDialog: (_displayArgs$ = displayArgs[5]) !== null && _displayArgs$ !== void 0 ? _displayArgs$ : undefined,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix assignment in expression

The code contains an assignment within an expression which can be confusing and error-prone.

- serviceWorkerPath: (_displayArgs$ = displayArgs[5]) !== null && _displayArgs$ !== void 0 ? _displayArgs$ : undefined,
+ const _displayArgs$ = displayArgs[5];
+ serviceWorkerPath: _displayArgs$ !== null && _displayArgs$ !== void 0 ? _displayArgs$ : undefined,

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

🧰 Tools
🪛 Biome (1.9.4)

[error] 5998-5998: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

src/clevertap.js Outdated
Comment on lines 551 to 557
StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
this.notifications._enableWebPush(enabled, applicationServerKey)
const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
if (isWebPushConfigPresent && isNotificationPushCalled) {
processSoftPrompt()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error handling for storage operations.

Storage operations could fail in private browsing mode or when storage is full. Consider wrapping the storage operations in try-catch blocks.

-      StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
-      this.notifications._enableWebPush(enabled, applicationServerKey)
-      const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
-      const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
-      if (isWebPushConfigPresent && isNotificationPushCalled) {
-        processSoftPrompt()
-      }
+      try {
+        StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
+        this.notifications._enableWebPush(enabled, applicationServerKey)
+        const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
+        const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
+        if (isWebPushConfigPresent && isNotificationPushCalled) {
+          processSoftPrompt()
+        }
+      } catch (error) {
+        logger.error('Failed to process web push setup:', error)
+        // Fallback: Enable web push without soft prompt
+        this.notifications._enableWebPush(enabled, applicationServerKey)
+      }
📝 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
StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
this.notifications._enableWebPush(enabled, applicationServerKey)
const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
if (isWebPushConfigPresent && isNotificationPushCalled) {
processSoftPrompt()
}
try {
StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
this.notifications._enableWebPush(enabled, applicationServerKey)
const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
if (isWebPushConfigPresent && isNotificationPushCalled) {
processSoftPrompt()
}
} catch (error) {
logger.error('Failed to process web push setup:', error)
// Fallback: Enable web push without soft prompt
this.notifications._enableWebPush(enabled, applicationServerKey)
}

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: 3

🔭 Outside diff range comments (1)
src/modules/webPushPrompt/prompt.js (1)

Line range hint 232-268: Add XSS protection for DOM manipulation.

The code directly manipulates DOM elements without sanitizing input, which could lead to XSS vulnerabilities.

Add input sanitization:

+const sanitizeId = (id) => {
+  return id.replace(/[^a-zA-Z0-9-_]/g, '')
+}

 const softPromptCard = document.getElementById('pnWrapper')
 const oldSoftPromptCard = document.getElementById('wzrk_wrapper')

 serviceWorkerRegistration.pushManager.subscribe(subscribeObj)
   .then((subscription) => {
     // ... existing code ...
     const existingBellWrapper = document.getElementById('bell_wrapper')

     if (existingBellWrapper) {
-      existingBellWrapper.parentNode.removeChild(existingBellWrapper)
+      existingBellWrapper.remove()
     }
     if (softPromptCard) {
-      softPromptCard.parentNode.removeChild(softPromptCard)
+      softPromptCard.remove()
     }
     if (oldSoftPromptCard) {
-      oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
+      oldSoftPromptCard.remove()
     }
🧹 Nitpick comments (2)
src/modules/webPushPrompt/prompt.js (1)

12-16: Consider using TypeScript or JSDoc for better type safety.

These global variables could benefit from type definitions to prevent potential type-related bugs and improve maintainability.

Add JSDoc type definitions:

+/** @type {string|null} */
 let logger = null
+/** @type {object|null} */
 let account = null
+/** @type {object|null} */
 let request = null
+/** @type {Array|null} */
 let displayArgs = null
+/** @type {string|null} */
 let fcmPublicKey = null
src/modules/notification.js (1)

38-45: Improve method documentation and add return type.

The setupWebPush method lacks proper documentation and return type.

Add JSDoc documentation:

+ /**
+  * Sets up web push notifications by calling the private setUpWebPush method
+  * @param {Array} displayArgs - Display arguments for web push setup
+  * @throws {Error} If displayArgs is invalid
+  * @returns {void}
+  */
  setupWebPush (displayArgs) {
    /* 
      A method in notification.js which can be accessed in prompt.js file to call the
      private method this.#setUpWebPush
    */
    this.#setUpWebPush(displayArgs)
  }
🧰 Tools
🪛 ESLint

[error] 39-39: Trailing spaces not allowed.

(no-trailing-spaces)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4922ba1 and b697bd1.

📒 Files selected for processing (3)
  • src/clevertap.js (2 hunks)
  • src/modules/notification.js (5 hunks)
  • src/modules/webPushPrompt/prompt.js (10 hunks)
🧰 Additional context used
🪛 ESLint
src/clevertap.js

[error] 553-553: Expected indentation of 8 spaces but found 10.

(indent)


[error] 554-554: Expected indentation of 8 spaces but found 10.

(indent)


[error] 555-555: Expected indentation of 8 spaces but found 10.

(indent)


[error] 556-556: Expected indentation of 8 spaces but found 10.

(indent)


[error] 557-557: Expected indentation of 10 spaces but found 12.

(indent)


[error] 558-558: Expected indentation of 8 spaces but found 10.

(indent)


[error] 559-559: Expected indentation of 6 spaces but found 8.

(indent)


[error] 560-560: Expected indentation of 8 spaces but found 10.

(indent)


[error] 561-561: Expected indentation of 6 spaces but found 8.

(indent)

src/modules/notification.js

[error] 39-39: Trailing spaces not allowed.

(no-trailing-spaces)

🔇 Additional comments (3)
src/modules/webPushPrompt/prompt.js (2)

39-49: ⚠️ Potential issue

Fix trailing spaces and improve error handling.

The code contains trailing spaces and could benefit from better error handling.

Apply these fixes:

- /* 
-    A method in notification.js which can be accessed in prompt.js file to call the
-    private method this.#setUpWebPush
-  */
+ /**
+  * A method in notification.js which can be accessed in prompt.js file to call the
+  * private method this.#setUpWebPush
+  * @param {Array} displayArgs - Display arguments for web push setup
+  * @throws {Error} If displayArgs is invalid
+  */
   setupWebPush (displayArgs) {
+    if (!Array.isArray(displayArgs)) {
+      throw new Error('displayArgs must be an array')
+    }
     this.#setUpWebPush(displayArgs)
   }

Likely invalid or redundant comment.


53-75: ⚠️ Potential issue

Add error handling for storage operations and improve type safety.

The function performs storage operations without error handling and type checking.

Add error handling and type checking:

 export const processSoftPrompt = () => {
+  try {
     const webPushConfig = StorageManager.readFromLSorCookie(WEBPUSH_CONFIG) || {}
     notificationHandler = new NotificationHandler({ logger, session: {}, request, account })
 
     if (!(Object.keys(webPushConfig).length > 0)) {
       notificationHandler.setApplicationServerKey(appServerKey)
       notificationHandler.setupWebPush(displayArgs)
       return
     }
     const { showBox, showBellIcon, boxType } = webPushConfig
 
     const { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback } = parseDisplayArgs(displayArgs)
     if (showBellIcon || (showBox && boxType === 'new')) {
       const enablePushParams = { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback, logger, request, account, fcmPublicKey }
       enablePush(enablePushParams)
     }
     if (showBox && boxType === 'old') {
       notificationHandler.setApplicationServerKey(appServerKey)
       notificationHandler.setupWebPush(displayArgs)
     }
     StorageManager.saveToLSorCookie('notificationPushCalled', false)
     StorageManager.saveToLSorCookie('applicationServerKeyReceived', false)
+  } catch (error) {
+    logger?.error('Failed to process soft prompt:', error)
+  }
 }

Likely invalid or redundant comment.

src/clevertap.js (1)

552-561: ⚠️ Potential issue

Fix indentation and improve error handling.

The code has inconsistent indentation and could benefit from better error handling.

Fix indentation and improve error handling:

     api.enableWebPush = (enabled, applicationServerKey) => {
       setServerKey(applicationServerKey)
       this.notifications._enableWebPush(enabled, applicationServerKey)
       try {
-          StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
-          const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
-          const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
-          if (isWebPushConfigPresent && isNotificationPushCalled) {
-            processSoftPrompt()
-          }
+        StorageManager.saveToLSorCookie('applicationServerKeyReceived', true)
+        const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
+        const isNotificationPushCalled = StorageManager.readFromLSorCookie('notificationPushCalled')
+        if (isWebPushConfigPresent && isNotificationPushCalled) {
+          processSoftPrompt()
+        }
       } catch (error) {
-          this.#logger.error('Could not read value from local storage', error)
+        this.#logger.error('Could not read value from local storage', error)
+        // Attempt to continue with web push setup without soft prompt
+        this.notifications._enableWebPush(enabled, applicationServerKey)
       }
     }

Likely invalid or redundant comment.

🧰 Tools
🪛 ESLint

[error] 553-553: Expected indentation of 8 spaces but found 10.

(indent)


[error] 554-554: Expected indentation of 8 spaces but found 10.

(indent)


[error] 555-555: Expected indentation of 8 spaces but found 10.

(indent)


[error] 556-556: Expected indentation of 8 spaces but found 10.

(indent)


[error] 557-557: Expected indentation of 10 spaces but found 12.

(indent)


[error] 558-558: Expected indentation of 8 spaces but found 10.

(indent)


[error] 559-559: Expected indentation of 6 spaces but found 8.

(indent)


[error] 560-560: Expected indentation of 8 spaces but found 10.

(indent)


[error] 561-561: Expected indentation of 6 spaces but found 8.

(indent)

Comment on lines +18 to +24
export const setNotificationHandlerValues = (notificationValues = {}) => {
logger = notificationValues.logger
account = notificationValues.account
request = notificationValues.request
displayArgs = notificationValues.displayArgs
fcmPublicKey = notificationValues.fcmPublicKey
}
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 validation for notificationValues object.

The function accepts an object but doesn't validate its properties, which could lead to runtime errors.

Add property validation:

 export const setNotificationHandlerValues = (notificationValues = {}) => {
+  if (typeof notificationValues !== 'object') {
+    logger?.error('notificationValues must be an object')
+    return
+  }
   logger = notificationValues.logger
   account = notificationValues.account
   request = notificationValues.request
   displayArgs = notificationValues.displayArgs
   fcmPublicKey = notificationValues.fcmPublicKey
 }
📝 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
export const setNotificationHandlerValues = (notificationValues = {}) => {
logger = notificationValues.logger
account = notificationValues.account
request = notificationValues.request
displayArgs = notificationValues.displayArgs
fcmPublicKey = notificationValues.fcmPublicKey
}
export const setNotificationHandlerValues = (notificationValues = {}) => {
if (typeof notificationValues !== 'object') {
logger?.error('notificationValues must be an object')
return
}
logger = notificationValues.logger
account = notificationValues.account
request = notificationValues.request
displayArgs = notificationValues.displayArgs
fcmPublicKey = notificationValues.fcmPublicKey
}

Comment on lines 77 to 90
export const parseDisplayArgs = (displayArgs) => {
if (displayArgs.length === 1 && isObject(displayArgs[0])) {
const { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback } = displayArgs[0]
return { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback }
}

return {
serviceWorkerPath: undefined,
skipDialog: displayArgs[5],
okCallback: undefined,
subscriptionCallback: undefined,
rejectCallback: undefined
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve input validation and error handling in parseDisplayArgs.

The function lacks proper input validation and error handling.

Add input validation and error handling:

 export const parseDisplayArgs = (displayArgs) => {
+  if (!displayArgs || !Array.isArray(displayArgs)) {
+    logger?.error('Invalid displayArgs: must be an array')
+    return {
+      serviceWorkerPath: undefined,
+      skipDialog: false,
+      okCallback: undefined,
+      subscriptionCallback: undefined,
+      rejectCallback: undefined
+    }
+  }
   if (displayArgs.length === 1 && isObject(displayArgs[0])) {
     const { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback } = displayArgs[0]
+    if (okCallback && typeof okCallback !== 'function') {
+      logger?.error('Invalid okCallback: must be a function')
+      return null
+    }
     return { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback }
   }

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

Comment on lines 46 to 69
push (...displayArgs) {
/*
To handle a potential race condition, two flags are stored in Local Storage:
- `webPushConfigResponseReceived`: Indicates if the backend's webPushConfig has been received (set during the initial API call without a session ID).
- `notificationPushCalled`: Tracks if `clevertap.notifications.push` was called before receiving the webPushConfig.

This ensures the soft prompt is rendered correctly:
- If `webPushConfigResponseReceived` is true, the soft prompt is processed immediately.
- Otherwise, `notificationPushCalled` is set to true, and the rendering is deferred until the webPushConfig is received.
*/
const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
const isApplicationServerKeyReceived = StorageManager.readFromLSorCookie('applicationServerKeyReceived')
setNotificationHandlerValues({
logger: this.#logger,
account: this.#account,
request: this.#request,
displayArgs,
fcmPublicKey: this.#fcmPublicKey
})
if (isWebPushConfigPresent && isApplicationServerKeyReceived) {
processSoftPrompt()
} else {
StorageManager.saveToLSorCookie('notificationPushCalled', true)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for race conditions.

The push method handles race conditions but lacks proper error handling.

Add error handling:

 push (...displayArgs) {
+  try {
     const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
     const isApplicationServerKeyReceived = StorageManager.readFromLSorCookie('applicationServerKeyReceived')
     setNotificationHandlerValues({
       logger: this.#logger,
       account: this.#account,
       request: this.#request,
       displayArgs,
       fcmPublicKey: this.#fcmPublicKey
     })
     if (isWebPushConfigPresent && isApplicationServerKeyReceived) {
       processSoftPrompt()
     } else {
       StorageManager.saveToLSorCookie('notificationPushCalled', true)
     }
+  } catch (error) {
+    this.#logger.error('Failed to process push notification:', error)
+  }
 }
📝 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
push (...displayArgs) {
/*
To handle a potential race condition, two flags are stored in Local Storage:
- `webPushConfigResponseReceived`: Indicates if the backend's webPushConfig has been received (set during the initial API call without a session ID).
- `notificationPushCalled`: Tracks if `clevertap.notifications.push` was called before receiving the webPushConfig.
This ensures the soft prompt is rendered correctly:
- If `webPushConfigResponseReceived` is true, the soft prompt is processed immediately.
- Otherwise, `notificationPushCalled` is set to true, and the rendering is deferred until the webPushConfig is received.
*/
const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
const isApplicationServerKeyReceived = StorageManager.readFromLSorCookie('applicationServerKeyReceived')
setNotificationHandlerValues({
logger: this.#logger,
account: this.#account,
request: this.#request,
displayArgs,
fcmPublicKey: this.#fcmPublicKey
})
if (isWebPushConfigPresent && isApplicationServerKeyReceived) {
processSoftPrompt()
} else {
StorageManager.saveToLSorCookie('notificationPushCalled', true)
}
push (...displayArgs) {
/*
To handle a potential race condition, two flags are stored in Local Storage:
- `webPushConfigResponseReceived`: Indicates if the backend's webPushConfig has been received (set during the initial API call without a session ID).
- `notificationPushCalled`: Tracks if `clevertap.notifications.push` was called before receiving the webPushConfig.
This ensures the soft prompt is rendered correctly:
- If `webPushConfigResponseReceived` is true, the soft prompt is processed immediately.
- Otherwise, `notificationPushCalled` is set to true, and the rendering is deferred until the webPushConfig is received.
*/
try {
const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
const isApplicationServerKeyReceived = StorageManager.readFromLSorCookie('applicationServerKeyReceived')
setNotificationHandlerValues({
logger: this.#logger,
account: this.#account,
request: this.#request,
displayArgs,
fcmPublicKey: this.#fcmPublicKey
})
if (isWebPushConfigPresent && isApplicationServerKeyReceived) {
processSoftPrompt()
} else {
StorageManager.saveToLSorCookie('notificationPushCalled', true)
}
} catch (error) {
this.#logger.error('Failed to process push notification:', error)
}

if (isWebPushConfigPresent && isApplicationServerKeyReceived) {
processSoftPrompt()
} else {
StorageManager.saveToLSorCookie('notificationPushCalled', true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this variable is used to defer the push call, consider renaming it to something like isNotificationPushCallDeferred to make its purpose more explicit. Using verb formats for boolean variables improves readability and aligns with common coding conventions.


const { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback } = parseDisplayArgs(displayArgs)
if (showBellIcon || (showBox && boxType === 'new')) {
const enablePushParams = { serviceWorkerPath, skipDialog, okCallback, subscriptionCallback, rejectCallback, logger, request, account, fcmPublicKey }
Copy link
Contributor

Choose a reason for hiding this comment

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

In this function try naming the boolean conditions in variables for better readability and maintenance.

@@ -34,14 +35,38 @@ export default class NotificationHandler extends Array {
this.#account = account
}

push (...displayArgs) {
setupWebPush (displayArgs) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we are exposing new function in a public API, please check if it can be avoided, in case there are some contraints try adding them in the comments.

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: 0

♻️ Duplicate comments (1)
src/modules/notification.js (1)

46-69: ⚠️ Potential issue

Add error handling for race conditions.

The push method handles race conditions but lacks proper error handling.

Apply this diff to add error handling:

 push (...displayArgs) {
+  try {
     const isWebPushConfigPresent = StorageManager.readFromLSorCookie('webPushConfigResponseReceived')
     const isApplicationServerKeyReceived = StorageManager.readFromLSorCookie('applicationServerKeyReceived')
     setNotificationHandlerValues({
       logger: this.#logger,
       account: this.#account,
       request: this.#request,
       displayArgs,
       fcmPublicKey: this.#fcmPublicKey
     })
     if (isWebPushConfigPresent && isApplicationServerKeyReceived) {
       processSoftPrompt()
     } else {
       StorageManager.saveToLSorCookie('notificationPushCalled', true)
     }
+  } catch (error) {
+    this.#logger.error('Failed to process push notification:', error)
+  }
 }
🧹 Nitpick comments (4)
clevertap.js (2)

5345-5372: Consider adding retry mechanism for config fetching

While the current implementation handles the race condition well, consider adding a retry mechanism with timeout for fetching web push config to improve reliability.

const MAX_RETRIES = 3;
const RETRY_DELAY = 2000;

function fetchWebPushConfigWithRetry(retries = 0) {
  if (retries >= MAX_RETRIES) {
    logger.error('Failed to fetch web push config after max retries');
    return;
  }
  
  try {
    // fetch config
    // on success, process config
  } catch (error) {
    setTimeout(() => {
      fetchWebPushConfigWithRetry(retries + 1);
    }, RETRY_DELAY);
  }
}

5915-5926: Consider adding telemetry for error tracking

To better understand and debug issues in production, consider adding telemetry for tracking web push configuration errors.

function trackWebPushError(error, context) {
  const errorData = {
    error: error.message,
    context,
    timestamp: Date.now()
  };
  // Send to analytics/monitoring system
}
src/modules/notification.js (2)

38-43: Consider reducing method visibility and improving documentation.

While this method is needed for prompt.js, exposing a wrapper around a private method could leak implementation details. Consider:

  1. Documenting the method's purpose, parameters, and return type using JSDoc.
  2. Evaluating if this method could be made private or moved to a shared utility.
-  setupWebPush (displayArgs) {
-  /*
-    A method in notification.js which can be accessed in prompt.js file to call the
-    private method this.#setUpWebPush
-  */
-    this.#setUpWebPush(displayArgs)
-  }
+  /**
+   * Sets up web push notifications with the provided display arguments.
+   * @param {Object} displayArgs - The arguments for configuring the web push display
+   * @private
+   */
+  setupWebPush (displayArgs) {
+    this.#setUpWebPush(displayArgs)
+  }

232-234: Extract duplicate cleanup code into a helper function.

The soft prompt cleanup code is duplicated in both success and error handlers.

+  #cleanupSoftPromptElements() {
+    const softPromptCard = document.getElementById('pnWrapper')
+    const oldSoftPromptCard = document.getElementById('wzrk_wrapper')
+    if (softPromptCard) {
+      softPromptCard.parentNode.removeChild(softPromptCard)
+    }
+    if (oldSoftPromptCard) {
+      oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
+    }
+  }

   serviceWorkerRegistration.pushManager.subscribe(subscribeObj)
     .then((subscription) => {
       // ... existing success handling code ...
-      if (softPromptCard) {
-        softPromptCard.parentNode.removeChild(softPromptCard)
-      }
-      if (oldSoftPromptCard) {
-        oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
-      }
+      this.#cleanupSoftPromptElements()
     }).catch((error) => {
       // ... existing error handling code ...
-      if (softPromptCard) {
-        softPromptCard.parentNode.removeChild(softPromptCard)
-      }
-      if (oldSoftPromptCard) {
-        oldSoftPromptCard.parentNode.removeChild(oldSoftPromptCard)
-      }
+      this.#cleanupSoftPromptElements()
     })

Also applies to: 263-268, 286-291

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b697bd1 and 0bf9c64.

⛔ Files ignored due to path filters (2)
  • clevertap.js.map is excluded by !**/*.map
  • clevertap.min.js is excluded by !**/*.min.js
📒 Files selected for processing (3)
  • clevertap.js (15 hunks)
  • src/clevertap.js (2 hunks)
  • src/modules/notification.js (5 hunks)
🔇 Additional comments (14)
clevertap.js (10)

5336-5342: LGTM! Method to expose private setUpWebPush functionality

The setupWebPush method provides a clean public interface to access the private #setUpWebPush method, maintaining proper encapsulation.


5345-5372: Improved web push notification handling with race condition prevention

The code now handles a potential race condition between receiving web push config and notification push calls by:

  1. Using local storage flags to track config and server key status
  2. Deferring soft prompt rendering until both are received
  3. Adding clear documentation of the approach

Line range hint 5563-5603: Cleanup of DOM elements after web push subscription

The code properly cleans up UI elements after successful web push subscription by removing:

  1. The soft prompt card
  2. The legacy soft prompt wrapper
  3. The bell icon wrapper

This prevents stale UI elements from persisting.


5624-5630: Proper error handling with UI cleanup

The error handler appropriately cleans up UI elements even when subscription fails, ensuring a good user experience.


5885-5897: Improved notification handler configuration

The setNotificationHandlerValues function centralizes the configuration of notification handler properties, making it easier to manage and modify notification settings.


5915-5926: Enhanced error handling for web push config processing

The code includes proper error handling when processing web push config:

  1. Logs errors appropriately
  2. Has a fallback mechanism to still process soft prompt
  3. Uses try-catch to prevent crashes

5929-5979: Well-structured soft prompt processing logic

The processSoftPrompt function:

  1. Properly checks for required configurations
  2. Handles different box types appropriately
  3. Cleans up storage flags after processing
  4. Has clear conditional logic for different prompt scenarios

5980-6005: Clean utility function for parsing display arguments

The parseDisplayArgs function:

  1. Handles both object and array argument formats
  2. Provides proper default values
  3. Has clear type handling
  4. Returns a consistent structure

6006-6018: Well-structured parameter destructuring

The code uses clean parameter destructuring to extract required values, making the code more readable and maintainable.


9157-9168: Improved web push enablement with proper error handling

The code:

  1. Updates storage flags appropriately
  2. Checks for required conditions before processing
  3. Has proper error handling and logging
  4. Maintains backward compatibility
src/modules/notification.js (2)

11-11: LGTM!

The import of utility functions from webPushPrompt/prompt aligns with the integration of soft prompt functionality into the push API.


68-68: Consider renaming the flag for better clarity.

Since this variable is used to defer the push call, consider renaming it to something like isNotificationPushCallDeferred.

src/clevertap.js (2)

46-46: LGTM!

The import statement correctly includes the necessary functions for web push setup.


552-561: LGTM! Error handling is properly implemented.

The implementation includes:

  1. Proper error handling for storage operations
  2. Appropriate error logging
  3. Correct checks for web push configuration state

@rohitniyerclevertap rohitniyerclevertap changed the base branch from master to develop January 30, 2025 10:50
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