Azure DevOps Extension SDK

repository·master·Indexed 41 days ago

https://github.com/microsoft/azure-devops-extension-sdk

A client-side JavaScript library (version 5.0.0) that enables Azure DevOps web extensions to communicate with the host frame. It provides essential capabilities for authentication, theme retrieval, and host environment context. The SDK supports ES Modules and AMD, featuring Nested App Authentication (NAA) for Microsoft Entra ID integration via MSAL.js and XDM for cross-domain messaging and remote procedure calls (RPC) between iframes.

Tokens
4.5K
Snippets
15
Records
20
Agent score
98%

What's inside Azure DevOps Extension SDK

  1. Use Nested App Authentication (NAA) with MSAL.js

    master

    Extensions requiring Microsoft Entra ID authentication can use the NAA bridge to acquire tokens via @azure/msal-browser (v5+). This bridge allows the Azure DevOps host frame to handle the authentication flow, bypassing cross-origin iframe limitations.

    Prerequisites

    1. Register a SPA app in the Azure Portal.
    2. Add the redirect URI: https://dev.azure.com/_public/_MsalPopup.
    3. Configure the required API permissions/scopes.

    Implementation Steps

    1. Call await SDK.enableNestedAppAuth() before creating the MSAL application.
    2. Use createNestablePublicClientApplication from @azure/msal-browser.
    3. Do not set redirectUri in your MSAL configuration; the bridge provides it automatically.
    4. Handle InteractionRequiredAuthError by falling back to acquireTokenPopup to handle user consent or interaction requirements.
    import * as SDK from "azure-devops-extension-sdk";
    import { createNestablePublicClientApplication, InteractionRequiredAuthError } from "@azure/msal-browser";
    
    await SDK.init();
    await SDK.enableNestedAppAuth();
    
    const pca = await createNestablePublicClientApplication({
        auth: { clientId: "your-entra-client-id" },
    });
    
    try {
        const result = await pca.acquireTokenSilent({
            scopes: ["https://graph.microsoft.com/.default"],
        });
    } catch (err) {
        if (err instanceof InteractionRequiredAuthError) {
            const result = await pca.acquireTokenPopup({
                scopes: ["https://graph.microsoft.com/.default"],
            });
        } else {
            throw err;
        }
    }
  2. Import the Azure DevOps Extension SDK

    master

    The SDK supports both ES Modules (ESM) and Asynchronous Module Definition (AMD).

    For modern projects using ES modules, add azure-devops-extension-sdk to your package.json dependencies and use the import syntax.

    For legacy projects using AMD, continue using the require function.

    // ES Module syntax
    import * as SDK from 'azure-devops-extension-sdk';
    // AMD module syntax
    require(['azure-devops-extension-sdk'], function(SDK) {
      // Use the module here
    });
  3. How XDM cross-domain messaging works

    master

    XDM (Cross-Domain Messaging) allows different frames or windows (e.g., an extension iframe and the Azure DevOps host) to communicate using Remote Procedure Calls (RPC).

    It relies on three main components:

    1. IXDMChannel: A communication pipe between two specific windows. It allows you to invoke methods on objects living in the remote window.
    2. IXDMObjectRegistry: A registry where a frame can expose its own objects (or factory functions) to be called by remote frames.
    3. IXDMChannelManager: A manager that tracks multiple channels and dispatches incoming postMessage events to the correct channel.

    The Workflow:

    • Exposing an API: A frame uses an IXDMObjectRegistry to register an object or a factory function with a unique instanceId.
    • Consuming an API: A remote frame uses an IXDMChannel to either invokeRemoteMethod directly or getRemoteObjectProxy to get a local proxy object that mirrors the remote one.
  4. Enable Nested App Authentication (NAA)

    master

    Nested App Authentication (NAA) allows MSAL (Microsoft Authentication Library) to delegate token acquisition to the Azure DevOps host frame. This is critical for extensions running in iframes to avoid cross-origin BroadcastChannel issues that block popup-based authentication.

    Workflow:

    1. Call init().
    2. Call enableNestedAppAuth().
    3. Create your MSAL instance using createNestablePublicClientApplication.

    Note: This requires the host to have NAA support enabled.

    import * as SDK from "azure-devops-extension-sdk";
    import { PublicClientApplication } from "@azure/msal-browser";
    
    async function setupAuth() {
        await SDK.init();
        await SDK.enableNestedAppAuth();
        
        // Use the specialized MSAL factory to delegate auth to the host
        const pca = await createNestablePublicClientApplication({
            auth: { clientId: "your-entra-client-id" }
        });
    }
  5. Initialize the Azure DevOps SDK

    master

    Every extension must call init() to establish a handshake with the Azure DevOps host. This process retrieves essential context like user information, host details, and initial configuration.

    If you set loaded: false in the options, the host will show a loading indicator until you explicitly call notifyLoadSucceeded(). If your extension encounters an error during startup, call notifyLoadFailed(error) to inform the host.

    To ensure the SDK is ready before calling other methods, either await init() or use the ready() function.

    import * as SDK from "azure-devops-extension-sdk";
    
    async function start() {
        await SDK.init({
            loaded: true, // Default is true
            applyTheme: true // Default is true
        });
        
        // Now you can safely call other SDK methods
        const user = SDK.getUser();
    }
    
    start();
  6. Trigger Entra ID consent proactively

    master

    If you want to force a consent prompt (for example, when a user clicks a 'Connect' button), use the prompt: "consent" option within the MSAL acquireTokenPopup method.

    await pca.acquireTokenPopup({
        scopes: ["https://graph.microsoft.com/.default"],
        prompt: "consent"
    });
  7. Initialize the SDK

    master

    You must call SDK.init() once your extension content has rendered. Your extension content will not be displayed to the user until the host frame is notified that initialization is complete.

    There are two ways to initialize:

    1. Standard Initialization: Call SDK.init() with no options. This notifies the host that you are loaded immediately.
    2. Deferred Initialization: Call SDK.init({ loaded: false }). This allows you to perform other SDK calls while your content is still loading (and hidden behind a spinner). Once your initial rendering is finished, call SDK.notifyLoadSucceeded() to make the content visible.
    import * as SDK from "azure-devops-extension-sdk";
    
    // Standard initialization
    SDK.init();
  8. Manage Nested App Authentication (NAA) lifecycle

    master

    To stop using the NAA bridge (e.g., during teardown or in tests), call SDK.disableNestedAppAuth().

    Warning: Once disabled, the bridge acts as a stub. Any subsequent MSAL token requests will return a BridgeDisabled error. No popups will be opened, and no interaction_in_progress locks will occur. To use NAA again, you must call SDK.enableNestedAppAuth().

    SDK.disableNestedAppAuth();
  9. Configure XDM serialization settings

    master

    By default, XDM does not serialize properties that begin with an underscore (_) across iframe boundaries to prevent accidental exposure of private state.

    If you need to include these properties, provide an ISerializationSettings object with includeUnderscoreProperties: true when calling invokeRemoteMethod.

  10. Resize the extension container

    master

    To ensure your extension UI is not clipped or unnecessarily large, call resize() to request the parent window to adjust the container size based on your content's dimensions.

    • resize(width?, height?): If width/height are omitted, it defaults to the current scrollWidth and scrollHeight of the document body.
    import * as SDK from "azure-devops-extension-sdk";
    
    // Automatically resize to fit current content size
    SDK.resize();
    
    // Resize to specific dimensions
    SDK.resize(800, 600);
  11. Retrieve extension and host context

    master

    Once initialized, you can access various context objects to understand the environment your extension is running in:

    • getUser(): Returns IUserContext (identity, name, display name, image URL).
    • getHost(): Returns IHostContext (organization ID, name, service version, and whether it is hosted/on-premises).
    • getExtensionContext(): Returns IExtensionContext (extension ID, publisher ID, and version).
    • getTeamContext(): Returns ITeamContext (team ID and name).
    • getPageContext(): Returns IPageContext (globalization and time zone settings).
    • getWebContext(): Returns IWebContext (project and team identifiers).
    • getConfiguration(): Returns the initial configuration object passed during the handshake.
    • getContributionId(): Returns the ID of the contribution that triggered the extension load.
  12. Get a remote object proxy using getRemoteObjectProxy

    master

    Instead of calling invokeRemoteMethod manually for every call, you can use getRemoteObjectProxy to obtain a local proxy object. This proxy behaves like a local object but transparently forwards all method calls to the remote instance via the XDM channel.

    Parameters:

    • instanceId: The unique ID of the registered object on the remote side.
    • contextData: (Optional) Data passed to the remote object's factory method.
    // Get a proxy for the 'myService' object registered remotely
    const myServiceProxy = await channel.getRemoteObjectProxy<any>("myService", { userId: 123 });
    
    // Method calls on the proxy are automatically sent via RPC
    const result = await myServiceProxy.doWork("some-param");