With your tab ready, let’s display user information by using the SDK. This will demonstrate how to:
Learn more at Waze Map Editor JavaScript SDK
Lets pull some User information from the SDK using the wmeSDK.State.getUserInfo() method and display it in our tab.
const userSession = wmeSDK.State.getUserInfo();
/*
Returns UserSession object:
{
isAreaManager: boolean;
isCountryManager: boolean;
rank: UserRank;
userName: string;
}
*/
As you can see, most of the information isn’t too useful for us in terms of creating a WME script - the important bits are already displayed in the User panel within WME. Let’s create a small User information section within our script.
First, we are going to need to change our tab so we have a space to add this information.
// Build HTML content for our tab
const section = document.createElement('div');
section.innerHTML = `
<div>
<h2>Our First Script!</h2>
<hr>
<div>
<h3>User Info</h3>
Username: <span id="wazedevUsername"></span><br>
Rank: <span id="wazedevRank"></span><br>
Area manager: <span id="wazedevAM"></span><br>
Country manager: <span id="wazedevCM"></span>
</div>
</div>
`;
We are adding spans so we can easily dynamically add the values from the SDK’s user session and we give them all unique IDs. I like to use the script name (or abbreviation) + a recognizable name for what it is. This serves two purposes:
So, in order to set these values we need to change our main() method. This is the method that gets called once the tab has been created in the side panel, by the SDK.
We’ll change our main() method to the below in order to fill in the information above.
Populate the user info in main():
function main() {
const userSession = wmeSDK.State.getUserInfo();
document.getElementById('wazedevUsername').textContent = userSession.userName;
document.getElementById('wazedevRank').textContent = userSession.rank;
document.getElementById('wazedevAM').textContent = userSession.isAreaManager ? 'Yes' : 'No';
document.getElementById('wazedevCM').textContent = userSession.isCountryManager ? 'Yes' : 'No';
console.log(`${SCRIPT_NAME}: Settings initialized!`);
}
The basic user session information is helpful, but what if we want more detailed statistics about a user’s editing activity? The WME SDK provides wmeSDK.DataModel.Users.getUserProfile() which gives us access to much more comprehensive user data.
wmeSDK.DataModel.Users.getUserProfile({ userName: "username" }) returns a Promise that resolves to a UserProfile object:
const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });
/*
UserProfile {
dailyEditCount: number[];
editCountByType: {
mapProblems: number;
placeUpdateRequests: number;
segmentHouseNumbers: number;
segments: number;
updateRequests: number;
venues: number;
};
totalEditCount: number;
}
*/
This gives us:
The SDK also provides wmeSDK.DataModel.Users.getUserProfileLink({ userName: "username" }) which returns a direct URL to that user’s profile page, allowing us to make the username clickable. Let’s take the user’s username from our userSession object as the input to getUserProfileLink().
const profileLink = wmeSDK.DataModel.Users.getUserProfileLink({ userName: userSession.userName });
Expand your tab HTML to display this additional information and make the username a clickable link:
const section = document.createElement('div');
section.innerHTML = `
<div>
<h2>Our First Script!</h2>
<hr>
<section>
<h3>User Info</h3>
Username: <a href="#" id="wazedevUsernameLink" target="_blank"><span id="wazedevUsername"></span></a><br>
Rank: <span id="wazedevRank"></span><br>
Area manager: <span id="wazedevAM"></span><br>
Country manager: <span id="wazedevCM"></span>
</section>
<hr>
<section>
<h3>Detailed Profile</h3>
<div id="wazedevProfileLoading">Loading profile data...</div>
<div id="wazedevProfileData" style="display: none;">
Total Edits: <span id="wazedevTotalEdits"></span><br>
Today's Edits: <span id="wazedevTodayEdits"></span>
</div>
</section>
<hr>
<section id="wazedevEditsByType" style="display: none;">
<h3>Edits by Type</h3>
Segments: <span id="wazedevSegments"></span><br>
Venues: <span id="wazedevVenues"></span><br>
Map Problems: <span id="wazedevMapProblems"></span><br>
Update Requests: <span id="wazedevUpdateRequests"></span><br>
Place Update Requests: <span id="wazedevPlaceUpdateRequests"></span><br>
Segment House Numbers: <span id="wazedevSegmentHouseNumbers"></span>
</section>
</div>
`;
Notice we’ve added a loading indicator and initially hide the profile data section. This provides better user experience since the profile data requires an API call that takes time to complete.
Now we need to update our main() method to handle the asynchronous profile data fetching:
async function main() {
const userSession = wmeSDK.State.getUserInfo();
// Set basic user info
document.getElementById('wazedevUsername').textContent = userSession.userName;
document.getElementById('wazedevRank').textContent = userSession.rank;
document.getElementById('wazedevAM').textContent = userSession.isAreaManager ? 'Yes' : 'No';
document.getElementById('wazedevCM').textContent = userSession.isCountryManager ? 'Yes' : 'No';
// Set profile link
const profileLink = wmeSDK.DataModel.Users.getUserProfileLink({ userName: userSession.userName });
document.getElementById('wazedevUsernameLink').href = profileLink;
// Fetch detailed user profile
try {
const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });
document.getElementById('wazedevProfileLoading').style.display = 'none';
document.getElementById('wazedevProfileData').style.display = 'block';
document.getElementById('wazedevEditsByType').style.display = 'block';
document.getElementById('wazedevTotalEdits').textContent = userProfile.totalEditCount.toLocaleString();
const todayEdits = userProfile.dailyEditCount[userProfile.dailyEditCount.length - 1] || 0;
document.getElementById('wazedevTodayEdits').textContent = todayEdits;
const editTypes = userProfile.editCountByType;
document.getElementById('wazedevSegments').textContent = editTypes.segments.toLocaleString();
document.getElementById('wazedevVenues').textContent = editTypes.venues.toLocaleString();
document.getElementById('wazedevMapProblems').textContent = editTypes.mapProblems.toLocaleString();
document.getElementById('wazedevUpdateRequests').textContent = editTypes.updateRequests.toLocaleString();
document.getElementById('wazedevPlaceUpdateRequests').textContent = editTypes.placeUpdateRequests.toLocaleString();
document.getElementById('wazedevSegmentHouseNumbers').textContent = editTypes.segmentHouseNumbers.toLocaleString();
console.log(`${SCRIPT_NAME}: Profile data loaded successfully`);
} catch (error) {
console.error(`${SCRIPT_NAME}: Error fetching user profile:`, error);
document.getElementById('wazedevProfileLoading').textContent = 'Error loading profile data';
}
console.log(`${SCRIPT_NAME}: Settings initialized!`);
}
Async/Await Pattern:
main() to an async function to handle the Promise returned by getUserProfile()getUserProfile())getUserInfo() returns data immediately (synchronous), while getUserProfile() returns a Promise (asynchronous)await pauses execution until the Promise resolves, making asynchronous code read like synchronous code// Synchronous SDK calls (immediate data):
const userSession = wmeSDK.State.getUserInfo(); // Returns data immediately
const isReady = wmeSDK.State.isReady(); // Returns boolean immediately
// Asynchronous SDK calls (return Promises):
const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: "user" }); // Returns Promise
const events = await wmeSDK.Events.once({ eventName: 'wme-ready' }); // Returns Promise
Our function demonstrates handling both types of SDK calls:
async function main() {
// Synchronous call - no await needed
const userSession = wmeSDK.State.getUserInfo();
// Use the sync data immediately
document.getElementById('wazedevUsername').textContent = userSession.userName;
// Asynchronous call - needs await and error handling
try {
const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });
// Handle async data...
} catch (error) {
// Handle async errors...
}
}
API Call Pattern for WME Scripts:
// For synchronous SDK calls:
const data = wmeSDK.Some.synchronousMethod();
document.getElementById('element').textContent = data.property;
// For asynchronous SDK calls:
try {
const data = await wmeSDK.Some.asynchronousMethod();
document.getElementById('loading').style.display = 'none';
document.getElementById('content').textContent = data.property;
} catch (error) {
console.error('Async call failed:', error);
document.getElementById('loading').textContent = 'Error loading data';
}
After implementing these changes, your tab should display:
Expected output:
Troubleshooting:
// ==UserScript==
// @name WazeDev First Script // Display name in Tampermonkey dashboard
// @namespace http://tampermonkey.net/ // Unique identifier (use your GreasyFork profile for updates)
// @version 0.1 // Version number for update tracking
// @description Learning to script! // Brief description of functionality
// @author You // Your name or username
// @include https://beta.waze.com/* // Run on beta WME (testing environment)
// @include https://www.waze.com/editor* // Run on production WME
// @include https://www.waze.com/*/editor* // Run on localized WME URLs
// @exclude https://www.waze.com/user/editor* // Don't run on user profile pages
// @exclude https://www.waze.com/editor/sdk/* // Don't run on SDK documentation
// @grant none // No special permissions needed
// ==/UserScript==
(function () {
'use strict';
const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
let wmeSDK; // Declare wmeSDK globally
if (window.SDK_INITIALIZED) {
console.log(`${SCRIPT_NAME}: SDK initialized...`);
window.SDK_INITIALIZED.then(bootstrap).catch((err) => {
console.error(`${SCRIPT_NAME}: SDK initialization failed`, err);
});
} else {
console.warn(`${SCRIPT_NAME}: SDK_INITIALIZED is undefined`);
}
function bootstrap() {
try {
wmeSDK = getWmeSdk({
scriptId: SCRIPT_NAME.replaceAll(' ', ''),
scriptName: SCRIPT_NAME,
});
Promise.all([wmeReady()])
.then(() => {
console.log(`${SCRIPT_NAME}: All dependencies are ready.`);
init();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error during bootstrap`, error);
});
} catch (error) {
console.error(`${SCRIPT_NAME}: Failed to initialize SDK`, error);
}
}
function wmeReady() {
return new Promise((resolve) => {
if (wmeSDK.State.isReady()) {
console.log(`${SCRIPT_NAME}: WME is ready.`);
resolve();
} else {
console.log(`${SCRIPT_NAME}: Waiting for WME to be ready...`);
wmeSDK.Events.once({ eventName: 'wme-ready' })
.then(() => {
console.log(`${SCRIPT_NAME}: WME is ready now.`);
resolve();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error while waiting for WME to be ready:`, error);
});
}
});
}
function init() {
console.log(`${SCRIPT_NAME}: Script initialized successfully!`);
// Build HTML content for our tab
const section = document.createElement('div');
section.innerHTML = `
<div>
<h2>Our First Script!</h2>
<hr>
<section>
<h3>User Info</h3>
Username: <a href="#" id="wazedevUsernameLink" target="_blank"><span id="wazedevUsername"></span></a><br>
Rank: <span id="wazedevRank"></span><br>
Area manager: <span id="wazedevAM"></span><br>
Country manager: <span id="wazedevCM"></span>
</section>
<hr>
<section>
<h3>Detailed Profile</h3>
<div id="wazedevProfileLoading">Loading profile data...</div>
<div id="wazedevProfileData" style="display: none;">
Total Edits: <span id="wazedevTotalEdits"></span><br>
Today's Edits: <span id="wazedevTodayEdits"></span>
</div>
</section>
<hr>
<section id="wazedevEditsByType" style="display: none;">
<h3>Edits by Type</h3>
Segments: <span id="wazedevSegments"></span><br>
Venues: <span id="wazedevVenues"></span><br>
Map Problems: <span id="wazedevMapProblems"></span><br>
Update Requests: <span id="wazedevUpdateRequests"></span><br>
Place Update Requests: <span id="wazedevPlaceUpdateRequests"></span><br>
Segment House Numbers: <span id="wazedevSegmentHouseNumbers"></span>
</section>
</div>
`;
// Register the script tab using the SDK
wmeSDK.Sidebar.registerScriptTab()
.then(({ tabLabel, tabPane }) => {
tabLabel.textContent = 'WazeDev';
tabLabel.title = SCRIPT_NAME;
tabPane.appendChild(section);
// Initialize the main() function after tab is created
main();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error creating script tab`, error);
});
}
async function main() {
const userSession = wmeSDK.State.getUserInfo();
// Set basic user info
document.getElementById('wazedevUsername').textContent = userSession.userName;
document.getElementById('wazedevRank').textContent = userSession.rank;
document.getElementById('wazedevAM').textContent = userSession.isAreaManager ? 'Yes' : 'No';
document.getElementById('wazedevCM').textContent = userSession.isCountryManager ? 'Yes' : 'No';
// Set profile link
const profileLink = wmeSDK.DataModel.Users.getUserProfileLink({ userName: userSession.userName });
document.getElementById('wazedevUsernameLink').href = profileLink;
// Fetch detailed user profile
try {
const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });
document.getElementById('wazedevProfileLoading').style.display = 'none';
document.getElementById('wazedevProfileData').style.display = 'block';
document.getElementById('wazedevEditsByType').style.display = 'block';
document.getElementById('wazedevTotalEdits').textContent = userProfile.totalEditCount.toLocaleString();
const todayEdits = userProfile.dailyEditCount[userProfile.dailyEditCount.length - 1] || 0;
document.getElementById('wazedevTodayEdits').textContent = todayEdits;
const editTypes = userProfile.editCountByType;
document.getElementById('wazedevSegments').textContent = editTypes.segments.toLocaleString();
document.getElementById('wazedevVenues').textContent = editTypes.venues.toLocaleString();
document.getElementById('wazedevMapProblems').textContent = editTypes.mapProblems.toLocaleString();
document.getElementById('wazedevUpdateRequests').textContent = editTypes.updateRequests.toLocaleString();
document.getElementById('wazedevPlaceUpdateRequests').textContent = editTypes.placeUpdateRequests.toLocaleString();
document.getElementById('wazedevSegmentHouseNumbers').textContent = editTypes.segmentHouseNumbers.toLocaleString();
console.log(`${SCRIPT_NAME}: Profile data loaded successfully`);
} catch (error) {
console.error(`${SCRIPT_NAME}: Error fetching user profile:`, error);
document.getElementById('wazedevProfileLoading').textContent = 'Error loading profile data';
}
console.log(`${SCRIPT_NAME}: Settings initialized!`);
}
})();
In the next section we add interactivity to our script. We’ll add an “Enabled” checkbox that users can toggle on/off, and we’ll save this preference so it persists between WME sessions.