Interacting with SDK - WazeDev/WazeDev.github.io GitHub Wiki
With your tab ready, let's display user information by using the SDK. This will demonstrate how to:
- Fetch data from the WME SDK
- Handle asynchronous operations
- Update the UI dynamically
- Provide user feedback during loading
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:
- It guarantees the ID we use will be unique (don't want to risk stepping on any other script's toes)
- If someone wants to see what script is modifying/adding the information, you can see this in the ID of the element.
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:
- dailyEditCount: An array of edit counts for the last 90 days (today is the last element)
- editCountByType: A breakdown of edits by category
- totalEditCount: The user's lifetime edit count
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:
- We changed
main()to anasync functionto handle the Promise returned bygetUserProfile() -
Important: Not all WME SDK calls return Promises - only some API calls do (like
getUserProfile()) -
getUserInfo()returns data immediately (synchronous), whilegetUserProfile()returns a Promise (asynchronous) -
awaitpauses 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- Only use async/await when the SDK method returns a Promise
- Check the SDK documentation to see if a method is asynchronous
- Methods that fetch data from servers (like user profiles) are typically async
- State checking methods (like getUserInfo()) are typically synchronous
- We wrap asynchronous API calls in try/catch blocks to handle potential failures
- Synchronous calls can fail too, but they throw errors immediately rather than rejecting Promises
- If an async API call fails, we show a user-friendly error message instead of breaking the script
- This prevents network issues or API changes from crashing your entire script
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...
}
}- We show a loading indicator immediately while the async API call is in progress
- Synchronous data (basic user info) appears instantly
- Asynchronous data (detailed profile) appears after the API call completes
- This provides immediate feedback and prevents confusion about whether the script is working
API Call Pattern for WME Scripts:
- Use this pattern based on whether the SDK method returns a Promise:
// 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:
- Basic user info loads immediately (username, rank, manager status)
- Loading indicator appears while fetching detailed profile
- Detailed statistics populate once the API call completes
- Clickable username that opens the user's profile page
Expected output:
- Total edits with proper number formatting (e.g., "1,234")
- Today's edit count from the daily array
- Breakdown by edit type with formatted numbers
Troubleshooting:
- "Loading profile data..." never disappears: Check console for API errors
- Numbers show as "0": User might be new or have no edits in that category
- Profile link doesn't work: Verify the username is being passed correctly
// ==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.