Firebase Users CSV Exporter

Export all Firebase Authentication users to CSV with pagination support

Size

11.4 KB

Version

1.0.1

Created

Nov 6, 2025

Updated

about 1 month ago

1// ==UserScript==
2// @name		Firebase Users CSV Exporter
3// @description		Export all Firebase Authentication users to CSV with pagination support
4// @version		1.0.1
5// @match		https://*.console.firebase.google.com/*
6// @icon		https://www.gstatic.com/mobilesdk/240501_mobilesdk/firebase_16dp.png
7// @grant		GM.xmlhttpRequest
8// ==/UserScript==
9(function() {
10    'use strict';
11
12    console.log('Firebase Users CSV Exporter loaded');
13
14    // Debounce function to avoid multiple rapid calls
15    function debounce(func, wait) {
16        let timeout;
17        return function executedFunction(...args) {
18            const later = () => {
19                clearTimeout(timeout);
20                func(...args);
21            };
22            clearTimeout(timeout);
23            timeout = setTimeout(later, wait);
24        };
25    }
26
27    // Function to create and add the export button
28    function addExportButton() {
29        // Check if we're on the authentication users page
30        if (!window.location.href.includes('/authentication/users')) {
31            console.log('Not on authentication users page');
32            return;
33        }
34
35        // Check if button already exists
36        if (document.getElementById('firebase-csv-export-btn')) {
37            console.log('Export button already exists');
38            return;
39        }
40
41        // Find the "Add User" button
42        const addUserButton = document.querySelector('button[data-test-id="add-user-button"]');
43        if (!addUserButton) {
44            console.log('Add User button not found, will retry...');
45            return;
46        }
47
48        console.log('Add User button found, creating export button');
49
50        // Create the export button
51        const exportButton = document.createElement('button');
52        exportButton.id = 'firebase-csv-export-btn';
53        exportButton.className = 'mdc-button mat-mdc-button-base mdc-button--raised mat-mdc-raised-button mat-primary';
54        exportButton.style.marginLeft = '12px';
55        exportButton.innerHTML = `
56            <span class="mat-mdc-button-persistent-ripple mdc-button__ripple"></span>
57            <span class="mdc-button__label">Export CSV</span>
58            <span class="mat-focus-indicator"></span>
59            <span class="mat-mdc-button-touch-target"></span>
60            <span class="mat-ripple mat-mdc-button-ripple"></span>
61        `;
62
63        // Add click event listener
64        exportButton.addEventListener('click', handleExportClick);
65
66        // Insert the button next to the Add User button
67        addUserButton.parentElement.insertBefore(exportButton, addUserButton.nextSibling);
68        console.log('Export button added successfully');
69    }
70
71    // Function to handle export button click
72    async function handleExportClick() {
73        const button = document.getElementById('firebase-csv-export-btn');
74        const originalText = button.querySelector('.mdc-button__label').textContent;
75        
76        try {
77            button.disabled = true;
78            button.querySelector('.mdc-button__label').textContent = 'Exporting...';
79            
80            console.log('Starting CSV export...');
81            
82            // Collect all user data from all pages
83            const allUsers = await collectAllUsers();
84            
85            if (allUsers.length === 0) {
86                alert('No users found to export');
87                return;
88            }
89
90            console.log(`Collected ${allUsers.length} users, generating CSV...`);
91            
92            // Generate CSV
93            const csv = generateCSV(allUsers);
94            
95            // Download CSV
96            downloadCSV(csv, 'firebase-users.csv');
97            
98            button.querySelector('.mdc-button__label').textContent = `Exported ${allUsers.length} users!`;
99            setTimeout(() => {
100                button.querySelector('.mdc-button__label').textContent = originalText;
101            }, 3000);
102            
103        } catch (error) {
104            console.error('Export failed:', error);
105            alert('Export failed: ' + error.message);
106            button.querySelector('.mdc-button__label').textContent = originalText;
107        } finally {
108            button.disabled = false;
109        }
110    }
111
112    // Function to collect all users from all pages
113    async function collectAllUsers() {
114        const allUsers = [];
115        let currentPage = 0;
116        
117        // Get current page users
118        const currentUsers = extractUsersFromCurrentPage();
119        allUsers.push(...currentUsers);
120        console.log(`Page ${currentPage + 1}: Collected ${currentUsers.length} users`);
121        
122        // Get pagination info
123        const paginationInfo = getPaginationInfo();
124        console.log('Pagination info:', paginationInfo);
125        
126        if (!paginationInfo) {
127            console.log('No pagination found, returning current page users');
128            return allUsers;
129        }
130        
131        const totalPages = Math.ceil(paginationInfo.total / paginationInfo.pageSize);
132        console.log(`Total pages to process: ${totalPages}`);
133        
134        // Navigate through all pages
135        for (let page = 1; page < totalPages; page++) {
136            const button = document.getElementById('firebase-csv-export-btn');
137            button.querySelector('.mdc-button__label').textContent = `Exporting... (${page + 1}/${totalPages})`;
138            
139            // Click next page button
140            const nextPageClicked = await clickNextPage();
141            if (!nextPageClicked) {
142                console.log('Could not click next page, stopping');
143                break;
144            }
145            
146            // Wait for page to load
147            await waitForPageLoad();
148            
149            // Extract users from this page
150            const pageUsers = extractUsersFromCurrentPage();
151            allUsers.push(...pageUsers);
152            console.log(`Page ${page + 1}: Collected ${pageUsers.length} users (Total: ${allUsers.length})`);
153        }
154        
155        return allUsers;
156    }
157
158    // Function to extract users from the current page
159    function extractUsersFromCurrentPage() {
160        const users = [];
161        const rows = document.querySelectorAll('table[role="grid"] tbody tr[role="row"]');
162        
163        console.log(`Found ${rows.length} user rows on current page`);
164        
165        rows.forEach((row, index) => {
166            try {
167                const cells = row.querySelectorAll('td[role="gridcell"]');
168                if (cells.length >= 4) {
169                    const identifier = cells[0].textContent.trim();
170                    const created = cells[2].textContent.trim();
171                    const signedIn = cells[3].textContent.trim();
172                    
173                    users.push({
174                        identifier,
175                        created,
176                        signedIn
177                    });
178                }
179            } catch (error) {
180                console.error(`Error extracting user from row ${index}:`, error);
181            }
182        });
183        
184        return users;
185    }
186
187    // Function to get pagination information
188    function getPaginationInfo() {
189        const paginationLabel = document.querySelector('.mat-mdc-paginator-range-label');
190        if (!paginationLabel) {
191            return null;
192        }
193        
194        const text = paginationLabel.textContent.trim();
195        // Format: "1 – 250 of 2784"
196        const match = text.match(/(\d+)\s*–\s*(\d+)\s*of\s*(\d+)/);
197        
198        if (match) {
199            return {
200                start: parseInt(match[1]),
201                end: parseInt(match[2]),
202                total: parseInt(match[3]),
203                pageSize: parseInt(match[2]) - parseInt(match[1]) + 1
204            };
205        }
206        
207        return null;
208    }
209
210    // Function to click the next page button
211    async function clickNextPage() {
212        const nextButton = document.querySelector('button.mat-mdc-paginator-navigation-next:not(.mat-mdc-button-disabled)');
213        if (!nextButton) {
214            console.log('Next button not found or disabled');
215            return false;
216        }
217        
218        nextButton.click();
219        return true;
220    }
221
222    // Function to wait for page to load
223    function waitForPageLoad() {
224        return new Promise((resolve) => {
225            // Wait for the table to update
226            let attempts = 0;
227            const maxAttempts = 50; // 5 seconds max
228            
229            const checkInterval = setInterval(() => {
230                attempts++;
231                
232                // Check if loading indicator is gone and table has content
233                const rows = document.querySelectorAll('table[role="grid"] tbody tr[role="row"]');
234                const loadingIndicator = document.querySelector('.mat-mdc-progress-spinner, .loading-spinner');
235                
236                if (rows.length > 0 && !loadingIndicator) {
237                    clearInterval(checkInterval);
238                    // Add a small delay to ensure data is fully rendered
239                    setTimeout(resolve, 500);
240                } else if (attempts >= maxAttempts) {
241                    clearInterval(checkInterval);
242                    console.log('Timeout waiting for page load');
243                    resolve();
244                }
245            }, 100);
246        });
247    }
248
249    // Function to generate CSV from user data
250    function generateCSV(users) {
251        const headers = ['Identifier', 'Created', 'Signed In'];
252        const rows = [headers];
253        
254        users.forEach(user => {
255            rows.push([
256                escapeCSVField(user.identifier),
257                escapeCSVField(user.created),
258                escapeCSVField(user.signedIn)
259            ]);
260        });
261        
262        return rows.map(row => row.join(',')).join('\n');
263    }
264
265    // Function to escape CSV fields
266    function escapeCSVField(field) {
267        if (field.includes(',') || field.includes('"') || field.includes('\n')) {
268            return `"${field.replace(/"/g, '""')}"`;
269        }
270        return field;
271    }
272
273    // Function to download CSV file
274    function downloadCSV(csvContent, filename) {
275        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
276        const link = document.createElement('a');
277        const url = URL.createObjectURL(blob);
278        
279        link.setAttribute('href', url);
280        link.setAttribute('download', filename);
281        link.style.visibility = 'hidden';
282        
283        document.body.appendChild(link);
284        link.click();
285        document.body.removeChild(link);
286        
287        console.log('CSV download initiated');
288    }
289
290    // Initialize the extension
291    function init() {
292        console.log('Initializing Firebase CSV Exporter...');
293        
294        // Try to add button immediately
295        addExportButton();
296        
297        // Watch for DOM changes to add button when page loads
298        const debouncedAddButton = debounce(addExportButton, 500);
299        const observer = new MutationObserver(debouncedAddButton);
300        
301        observer.observe(document.body, {
302            childList: true,
303            subtree: true
304        });
305        
306        // Also check periodically in case mutation observer misses it
307        const checkInterval = setInterval(() => {
308            if (document.getElementById('firebase-csv-export-btn')) {
309                clearInterval(checkInterval);
310            } else {
311                addExportButton();
312            }
313        }, 2000);
314    }
315
316    // Start when DOM is ready
317    if (document.readyState === 'loading') {
318        document.addEventListener('DOMContentLoaded', init);
319    } else {
320        init();
321    }
322
323})();
Firebase Users CSV Exporter | Robomonkey