diff --git a/.devrev/repo.yml b/.devrev/repo.yml new file mode 100644 index 0000000..a1d97bf --- /dev/null +++ b/.devrev/repo.yml @@ -0,0 +1 @@ +deployable: true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e03eee7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global code owner +* @dhananjayj-dev diff --git a/example/css/index.css b/example/css/index.css new file mode 100644 index 0000000..2e4898b --- /dev/null +++ b/example/css/index.css @@ -0,0 +1,142 @@ +/* General Styles */ +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: #F2F2F7; + margin: 0; + padding: 20px; +} + +/* Header */ +.header { + display: flex; + align-items: center; + padding: 10px; + background-color: #f5f5f5; + border-bottom: 1px solid #ddd; + margin: -20px -20px 20px -20px; +} + +#backButton { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + padding: 10px; + color: #333; + position: absolute; + left: 20px; +} + +.header h1 { + flex: 1; + text-align: center; + margin: 0; + font-size: 20px; +} + +/* Sections */ +.section { + margin-top: 20px; + padding: 10px; + background: #fff; + border-radius: 10px; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Section Titles */ +h2 { + font-size: 14px; + text-transform: uppercase; + color: gray; + margin-bottom: 5px; +} + +/* Lists */ +ul { + list-style: none; + padding: 0; +} +li { + padding: 10px; + margin: 5px 0; + border-radius: 8px; + transition: background-color 0.2s ease; +} + +/* Hover effect */ +li:hover { + background-color: #f0f0f0; +} + +/* Click/active feedback */ +li:active { + background-color: #d0d0d0; +} + +.status-list li, +.feature-list li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0; + font-size: 16px; + border-bottom: 1px solid #ddd; + color: black; + cursor: pointer; +} + +/* Feature List */ +.feature-list a { + text-decoration: none; + color: black; + font-size: 16px; +} + +/* Circular Checkbox */ +.circular-checkbox { + width: 20px; + height: 20px; + border-radius: 50%; + border: 2px solid grey; + display: inline-block; + text-align: center; + line-height: 20px; + font-size: 14px; + font-weight: bold; + cursor: pointer; +} + +.checked { + background-color: blue; + border-color: blue; + color: white; +} + +/* Input Fields */ +.input-group input { + width: 80%; + padding: 10px; + margin: 5px 0; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; +} + +/* Action Items */ +.action-item { + color: white; + text-align: center; + padding: 10px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +/* Large Scrollable List Items */ +.card { + background-color: #fff; + border-radius: 10px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin-bottom: 10px; + padding: 15px; +} diff --git a/example/delayedScreen.html b/example/delayedScreen.html new file mode 100644 index 0000000..25ece1d --- /dev/null +++ b/example/delayedScreen.html @@ -0,0 +1,27 @@ + + + + + + Delayed Screen + + + +
+ +

Delayed Screen

+
+
+

This screen opened after a 2 seconds delay

+
+ + + + + diff --git a/example/identification.html b/example/identification.html new file mode 100644 index 0000000..ac5ba93 --- /dev/null +++ b/example/identification.html @@ -0,0 +1,23 @@ + + + + + + Identification + + + +
+ +

Identification

+
+ +
+ +
+ + + + + + diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..6e1704a --- /dev/null +++ b/example/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + DevRevSDK Sample + + +
+ +

DevRev SDK Sample

+
+ +
+ +
+ + + + + + diff --git a/example/js/app.js b/example/js/app.js new file mode 100644 index 0000000..b9736ce --- /dev/null +++ b/example/js/app.js @@ -0,0 +1,36 @@ +document.addEventListener('DOMContentLoaded', function() { + const backButton = document.getElementById('backButton'); + if (backButton) { + backButton.addEventListener('click', function() { + window.history.back(); + }); + } +}); + +// Cordova Device Ready Event +document.addEventListener('deviceready', function() { + const deviceReadyElement = document.getElementById('deviceready'); + deviceReadyElement?.classList.add('ready'); + + deviceReadyElement?.classList.add('configuring'); + DevRev.configure('', function() { + console.log('DevRev SDK configured successfully.'); + deviceReadyElement?.classList.remove('configuring'); + deviceReadyElement?.classList.add('configured'); + }, function(error) { + console.error('Failed to configure DevRev SDK:', error); + deviceReadyElement?.classList.remove('configuring'); + deviceReadyElement?.classList.add('error'); + }); + + cordova.plugins.firebase.messaging.requestPermission(); + + cordova.plugins.firebase.messaging.getToken().then(function(token) { + console.log('Got device token: ', token); + }); + cordova.plugins.firebase.messaging.onTokenRefresh(function(refreshedToken) { + console.log('Refreshed FCM token:', refreshedToken); + }); + + console.log('Running cordova-' + cordova.platformId + '@' + cordova.version); +}, false); diff --git a/example/js/feature.js b/example/js/feature.js new file mode 100644 index 0000000..81be218 --- /dev/null +++ b/example/js/feature.js @@ -0,0 +1,493 @@ +// Feature list for different screens +const featureData = { + 'index.html': [ + { + title: 'Features', + list: [ + { text: 'Identification', link: 'identification.html' }, + { text: 'Push Notifications', link: 'pushNotifications.html' }, + { text: 'Support', link: 'support.html' }, + { text: 'Session Analytics', link: 'sessionAnalytics.html' } + ] + }, + { + title: 'Web View', + list: [ + { text: 'Open Web View', link: 'webView.html' } + ] + }, + { + title: 'Large Scrollable List', + list: [ + { text: 'Open Large Scrollable List', link: 'largeScrollableList.html' } + ] + }, + { + title: 'Debug', + list: [ + { + text: 'Simulate crash', + action: () => { + throw new Error('Simulated crash'); + } + }, + { + text: 'Simulate ANR', + action: () => { + if (cordova.platformId === 'android') { + const startTime = Date.now(); + while (Date.now() - startTime < 5000) { + void 0; + } + } + }, + condition: () => cordova.platformId === 'android' + } + ] + }, + { + title: 'Animation', + list: [ + { + text: 'Animation', + action: () => { + const elements = document.querySelectorAll('a'); + const animationElement = Array.from(elements).find(el => el.textContent === 'Animation'); + if (animationElement) { + animationElement.style.transition = 'all 2s'; + animationElement.style.transform = 'scale(1.5)'; + + setTimeout(() => { + animationElement.style.transform = 'scale(1)'; + }, 2000); + } + }, + condition: () => cordova.platformId === 'android' + } + ], + condition: () => cordova.platformId === 'android' + } + ], + 'identification.html': [ + { + title: 'Unverified User', + list: [ + { + text: 'Identify User', + type: 'input-group', + inputs: [ + { + id: 'unverifiedUserId', + type: 'text', + placeholder: 'User ID' + } + ], + action: () => { + const userId = document.getElementById('unverifiedUserId').value; + if (userId) { + var identity = { + userID: userId + }; + currentUserId = userId; + DevRev.identifyUnverifiedUser(identity); + } + } + } + ] + }, + { + title: 'Verified User', + list: [ + { + text: 'Verify User', + type: 'input-group', + inputs: [ + { + id: 'verifiedUserId', + type: 'text', + placeholder: 'User ID' + }, + { + id: 'sessionToken', + type: 'text', + placeholder: 'Session Token' + } + ], + action: () => { + const userId = document.getElementById('verifiedUserId').value; + const sessionToken = document.getElementById('sessionToken').value; + if (userId && sessionToken) { + currentUserId = userId; + DevRev.verifyUser(userId, sessionToken); + } + } + } + ] + }, + { + title: 'Update User', + list: [ + { + text: 'Update User', + type: 'input-group', + inputs: [ + { + id: 'email', + type: 'email', + placeholder: 'New Email' + } + ], + action: () => { + if (!currentUserId) { + alert('Please identify or verify a user first'); + return; + } + + const email = document.getElementById('email').value; + + const identity = { + userID: currentUserId, + userTraits: { + email: email + } + }; + + DevRev.updateUser(identity); + } + } + ] + }, + { + title: 'Logout', + list: [ + { text: 'Logout', action: () => logout() } + ] + } + ], + 'pushNotifications.html': [ + { title: 'Push Notifications', + list: [ + { text: 'Register for Push Notifications', action: () => registerDevice() }, + { text: 'Unregister from Push Notifications', action: () => unregisterDevice() } + ] + } + ], + 'support.html': [ + { title: 'Support', + list: [ + { text: 'Support Chat', action: () => DevRev.createSupportConversation() }, + { text: 'Support View', action: () => DevRev.showSupport() } + ] + } + ], + + 'sessionAnalytics.html': [ + { title: 'Session Monitoring', + list: [ + { text: 'Stop Monitoring', action: () => { + DevRev.stopAllMonitoring( + () => alert('Monitoring stopped successfully'), + (error) => alert('Failed to stop monitoring: ' + error) + ); + }}, + { text: 'Resume Monitoring', action: () => { + DevRev.resumeAllMonitoring( + () => alert('Monitoring resumed successfully'), + (error) => alert('Failed to resume monitoring: ' + error) + ); + }} + ] + }, + { + title: 'Session Recording', + list: [ + { text: 'Start Recording', action: () => { + DevRev.startRecording( + () => alert('Recording started successfully'), + (error) => alert('Failed to start recording: ' + error) + ); + }}, + { text: 'Stop Recording', action: () => { + DevRev.stopRecording( + () => alert('Recording stopped successfully'), + (error) => alert('Failed to stop recording: ' + error) + ); + }}, + { text: 'Pause Recording', action: () => { + DevRev.pauseRecording( + () => alert('Recording paused successfully'), + (error) => alert('Failed to pause recording: ' + error) + ); + }}, + { text: 'Resume Recording', action: () => { + DevRev.resumeRecording( + () => alert('Recording resumed successfully'), + (error) => alert('Failed to resume recording: ' + error) + ); + }}, + { text: 'Pause User Interaction Tracking', action: () => { + DevRev.pauseUserInteractionTracking( + () => alert('User interaction tracking paused'), + (error) => alert('Failed to pause user interaction tracking: ' + error) + ); + }}, + { text: 'Resume User Interaction Tracking', action: () => { + DevRev.resumeUserInteractionTracking( + () => alert('User interaction tracking has resumed'), + (error) => alert('Failed to resume user interaction tracking: ' + error) + ); + }} + ] + }, + { + title: 'Timer', + list: [ + { text: 'Start Timer', action: () => { + let properties = {'id': 'foo-bar-123'}; + DevRev.startTimerWithProperties('Sample Timer', properties, + () => alert('Timer started successfully'), + (error) => alert('Failed to start timer with properties: ' + error) + ); + }}, + { text: 'End Timer', action: () => { + let properties = {'id': 'foo-bar-123'}; + DevRev.endTimerWithProperties('Sample Timer', properties, + () => alert('Timer ended successfully'), + (error) => alert('Failed to end timer with properties: ' + error) + ); + }} + ] + }, + { + title: 'Manual Masking / Unmasking', + list: [ + { + text: 'Manually Masked UI Item', + type: 'label', + className: 'devrev-mask' + }, + { + text: 'Manually Unmasked UI Item', + type: 'input', + className: 'devrev-unmask', + placeholder: 'Manually Unmasked UI Item' + } + ] + }, + { + title: 'On-Demand Session', + list: [ + { + text: 'Process On-Demand Session', + action: () => { + DevRev.processAllOnDemandSessions( + () => alert('On-demand sessions processed successfully'), + (error) => alert('Failed to process on-demand sessions: ' + error) + ); + } + } + ] + }, + { + title: 'Delayed Screen', + list: [ + { + text: 'Navigate to the Delayed Screen', + action: () => { + if (cordova.platformId === 'android') { + DevRev.setInScreenTransitioning(true); + setTimeout(function() { + window.location.href = 'delayedScreen.html'; + }, 2000); + } + } + } + ], + condition: () => cordova.platformId === 'android' + }, + ] +}; + +let currentUserId = null; + +function logout() { + if(device.uuid) { + DevRev.logout(device.uuid, + () => alert('Logged out successfully'), + (error) => alert('Failed to logout: ' + error) + ); + } else { + alert('Device UUID not found'); + } +} + +async function registerDevice() { + try { + if (!window.device?.uuid) { + alert('Device UUID not available'); + return; + } + await cordova.plugins.firebase.messaging.requestPermission(); + + const token = await cordova.plugins.firebase.messaging.getToken(); + if (!token) { + alert('Failed to retrieve device token'); + return; + } + + console.log('Got device token:', token); + DevRev.registerDeviceToken(token, window.device.uuid, + () => alert('Device registered successfully for push notifications'), + (error) => alert('Failed to register device: ' + error) + ); + + } catch (error) { + console.error('Registration failed:', error); + alert('Registration failed: ' + error.message); + } +} + +async function unregisterDevice() { + if (!window.device?.uuid) { + alert('Device UUID not available'); + return; + } + + DevRev.unregisterDevice(window.device.uuid, + () => alert('Device unregistered successfully from push notifications'), + (error) => alert('Failed to unregister device: ' + error) + ); +} + +function renderFeatureList() { + const list = document.getElementById('featureList'); + if (!list) return; + + const screen = window.location.pathname.split('/').pop(); + const featureList = featureData[screen] || []; + + list.innerHTML = ''; + + featureList.forEach(section => { + if(section.condition && !section.condition()) { + return; + } + if (section.title) { + const header = document.createElement('h3'); + header.textContent = section.title; + list.appendChild(header); + } + + if (section.list) { + const ul = document.createElement('ul'); + ul.className = section.title === 'Manual Masking / Unmasking' ? 'input-group' : 'feature-list'; + + section.list.forEach(item => { + // Skip items that don't meet their condition + if (item.condition && !item.condition()) { + return; + } + + if (item.type === 'input-group') { + // Create input group container + const inputGroup = document.createElement('div'); + inputGroup.className = 'input-group'; + + // Add inputs + item.inputs.forEach(input => { + const inputElement = document.createElement('input'); + inputElement.type = input.type; + inputElement.id = input.id; + inputElement.placeholder = input.placeholder; + inputGroup.appendChild(inputElement); + }); + + list.appendChild(inputGroup); + + // Create button list item + const li = document.createElement('li'); + li.style.cursor = 'pointer'; + + if (item.action) { + li.addEventListener('click', (e) => { + e.preventDefault(); + item.action(); + console.log(`"${item.text}" executed successfully.`); + }); + } + + const a = document.createElement('a'); + a.textContent = item.text; + a.href = '#'; + a.style.pointerEvents = 'none'; + a.style.textDecoration = 'none'; + a.style.color = 'inherit'; + + li.appendChild(a); + ul.appendChild(li); + } else if (item.type === 'input') { + const li = document.createElement('li'); + const input = document.createElement('input'); + input.type = 'text'; + input.placeholder = item.placeholder; + input.className = item.className; + li.appendChild(input); + ul.appendChild(li); + } else if (item.type === 'label') { + const li = document.createElement('li'); + const label = document.createElement('label'); + label.textContent = item.text; + label.className = item.className; + li.appendChild(label); + ul.appendChild(li); + } else { + const li = document.createElement('li'); + li.style.cursor = 'pointer'; + + if (item.link) { + li.addEventListener('click', () => { + window.location.href = item.link; + }); + } else if (item.action) { + li.addEventListener('click', (e) => { + e.preventDefault(); + item.action(); + console.log(`"${item.text}" executed successfully.`); + }); + } + + const a = document.createElement('a'); + a.textContent = item.text; + a.href = item.link || '#'; + a.style.pointerEvents = 'none'; + a.style.textDecoration = 'none'; + a.style.color = 'inherit'; + + li.appendChild(a); + ul.appendChild(li); + } + }); + list.appendChild(ul); + } + }); +} + +document.addEventListener('DOMContentLoaded', function() { + renderFeatureList(); + + const currentPage = window.location.pathname.split('/').pop(); + if (currentPage === 'sessionAnalytics.html') { + const properties = { + 'page': 'session_analytics', + 'timestamp': new Date().toISOString() + }; + DevRev.addSessionProperties(properties, + () => console.log('Session properties added successfully'), + (error) => console.error('Failed to add session properties:', error) + ); + DevRev.trackScreenName('session-analytics', + () => console.log('Screen name tracked successfully'), + (error) => console.error('Failed to track screen name:', error) + ); + } +}); diff --git a/example/largeScrollableList.html b/example/largeScrollableList.html new file mode 100644 index 0000000..5d8fc49 --- /dev/null +++ b/example/largeScrollableList.html @@ -0,0 +1,32 @@ + + + + Large Scrollable List + + + +
+ +

Large Scrollable List

+
+ +
+
+ +
+
+ + + + diff --git a/example/pushNotifications.html b/example/pushNotifications.html new file mode 100644 index 0000000..834ccd9 --- /dev/null +++ b/example/pushNotifications.html @@ -0,0 +1,22 @@ + + + + + + + Document + + +
+ +

Push Notifications

+
+
+ +
+ + + + + + diff --git a/example/sessionAnalytics.html b/example/sessionAnalytics.html new file mode 100644 index 0000000..f9dce2b --- /dev/null +++ b/example/sessionAnalytics.html @@ -0,0 +1,23 @@ + + + + + + + Document + + +
+ +

Session Analytics

+
+ +
+ +
+ + + + + + diff --git a/example/support.html b/example/support.html new file mode 100644 index 0000000..d7e7d91 --- /dev/null +++ b/example/support.html @@ -0,0 +1,22 @@ + + + + + + + Document + + +
+ +

Support

+
+ +
+ +
+ + + + + diff --git a/example/webView.html b/example/webView.html new file mode 100644 index 0000000..fd18341 --- /dev/null +++ b/example/webView.html @@ -0,0 +1,56 @@ + + + + + + Sample Page with Masking + + + +

Sample HTML for Web View

+

This page demonstrates content masking within a Web View.

+
+ + +

This field (devrev-mask) is masked in recordings.

+
+ +
+ + +

This password field (type="password") is automatically masked by the SDK without requiring the devrev-mask class.

+
+ +
+ + +

This field (devrev-unmask) is visible in recordings.

+
+ +
+ + +

This number (devrev-mask) is masked in recordings.

+
+ +

This paragraph contains a masked phrase and an unmasked phrase within the text.

+ +
+

This entire div and its contents are masked:

+ + +
+ +
+ + +