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