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

Getting Basic User Info

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.

Expanding the Tab HTML

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:

  1. It guarantees the ID we use will be unique (don't want to risk stepping on any other script's toes)
  2. 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.

Populating the User Information

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!`);
}

Getting Detailed User Profile Data

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.

Handling Asynchronous Data

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!`);
}

Key Changes and Concepts Introduced

Async/Await Pattern:

  • We changed main() to an async function to handle the Promise returned by getUserProfile()
  • Important: Not all WME SDK calls return Promises - only some API calls do (like 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

When to Use Async/Await:

  • 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

Error Handling Best Practices:

  • 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

Mixed Sync/Async Pattern:

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...
    }
}

Progressive UI Updates:

  • 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';
}

Testing Your Enhanced Tab

After implementing these changes, your tab should display:

  1. Basic user info loads immediately (username, rank, manager status)
  2. Loading indicator appears while fetching detailed profile
  3. Detailed statistics populate once the API call completes
  4. 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

Complete Script So Far

// ==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.

⚠️ **GitHub.com Fallback** ⚠️