Discord Embedded App SDK

repository·main·Indexed 23 days ago

https://github.com/discord/embedded-app-sdk

The Embedded App SDK enables developers to build rich, multiplayer games and social experiences (Activities) that run inside an iframe within the Discord client across desktop, web, and mobile. It provides tools for session authentication, event subscriptions, and integration with Discord's In-Application Purchase (IAP) system, including SKU management and entitlement verification.

Tokens
14K
Snippets
36
Records
71
Agent score
79%

What's inside @discord/embedded-app-sdk

  1. Core Concepts of Discord In-Application Purchases (IAP)

    main

    Discord's IAP system uses two primary abstractions to manage monetization:

    • SKU (Stock Keeping Unit): A unique identifier for a specific offering (a "menu item"). SKUs cannot be deleted once created. There are two types:
      • SKUType.DURABLE (2): One-time purchases that permanently entitle a user (e.g., skins, characters).
      • SKUType.CONSUMABLE (3): Items that can be purchased multiple times and marked as consumed (e.g., potions, tickets).
    • Entitlement: A "purchase receipt" representing a successful purchase. It records that a specific User has the right to use a specific SKU.
  2. Security Best Practices: Trust (the RPC Server), but Verify (via API)

    main

    Data obtained via RPC Commands and Events (client-side) cannot be fully trusted because a malicious actor could spoof the RPC connection to claim entitlements.

    Recommended Pattern:

    1. Optimistically use client-side RPC Commands and Events to fetch SKUs and Entitlements for a smooth user experience.
    2. Verify the results by calling the Discord HTTP API from your application backend.

    Always treat the Discord HTTP API as the single source of truth for premium products and features.

  3. Initialize and set up the DiscordSDK

    main

    To use the SDK, import the DiscordSDK class and instantiate it with your OAuth2 Client ID.

    Follow these steps in your setup sequence:

    1. Wait for Ready: Call await discordSdk.ready() to wait for the READY payload from the Discord client.
    2. Authorize: Use discordSdk.commands.authorize to trigger the OAuth permission modal. You must specify client_id, response_type, state, prompt, and the required scope array (e.g., ['identify', 'applications.commands']).
    3. Exchange Code for Token: Send the returned code to your application's server to retrieve an access_token.
    4. Authenticate: Call await discordSdk.commands.authenticate({ access_token }) to authenticate the session with the Discord client.

    Note: Replace YOUR_OAUTH2_CLIENT_ID with your actual credentials found in the Discord Developer Portal.

    import {DiscordSDK} from '@discord/embedded-app-sdk';
    const discordSdk = new DiscordSDK(YOUR_OAUTH2_CLIENT_ID);
    
    async function setup() {
      // Wait for READY payload from the discord client
      await discordSdk.ready();
    
      // Pop open the OAuth permission modal and request for access to scopes listed in scope array below
      const {code} = await discordSdk.commands.authorize({
        client_id: YOUR_OAUTH2_CLIENT_ID,
        response_type: 'code',
        state: '',
        prompt: 'none',
        scope: ['identify', 'applications.commands'],
      });
    
      // Retrieve an access_token from your application's server
      const response = await fetch('/.proxy/api/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          code,
        }),
      });
      const {access_token} = await response.json();
    
      // Authenticate with Discord client (using the access_token)
      auth = await discordSdk.commands.authenticate({
        access_token,
      });
    }
  4. Setup Requirements for In-Application Purchases

    main

    To use IAP, your application must meet the following criteria:

    1. Embedded Application: Your application must already be configured as an Embedded Application.
    2. Team Ownership: Your application must be owned by a Team, not an individual.
    3. Payout Registration: The Team Owner must register payout settings via the Developer Portal. This involves selecting the team and following the redirect to Stripe Connect to provide legal information.
    4. EMBEDDED_IAP Flag: You must contact your Discord point of contact to request the EMBEDDED_IAP (1 << 3) flag be enabled for your Application ID. This flag is required to create, manage, and sell SKUs.
  5. Migrate from activity-iframe-sdk to @discord/embedded-app-sdk

    main

    If you are migrating from activity-iframe-sdk v2 to @discord/embedded-app-sdk v1.0.0+, follow these steps:

    1. Uninstall the old package and install the new one:
      npm uninstall @discord-external/activity-iframe-sdk
      npm install @discord/embedded-app-sdk
    2. Remove private registry configurations: Remove any code or files (like .npmrc) used to install private GitHub packages.
    3. Resolve TypeScript errors: Run tsc and address errors. Most errors will stem from subscribe usage or removed commands/events.
    4. Update event/command usage: Use the EventPayloadData type to fix subscription shapes and replace removed APIs with their recommended alternatives.
    5. Test functionality: Perform basic tests of your activity's key features.
    npm uninstall @discord-external/activity-iframe-sdk
    npm install @discord/embedded-app-sdk
  6. Migrate from Activity Iframe SDK v1 to Embedded App SDK v2

    main

    Version 2.0.0 of the SDK introduces breaking changes to the command return shape. The primary change is the standardization of command responses: whereas v1 distinguished between "Payload" and "Data" command shapes, v2 treats all commands as "Data" commands. This means all commands now return the desired data directly, rather than wrapping it in a .data property. This change also provides significantly improved TypeScript type inference.

    // V1 (Old)
    // "Data" command
    const {code} = await sdk.commands.authorize();
    
    // "Payload" command
    const {
      cmd,
      data: {permissions},
      evt,
      nonce,
    } = await discordSdk.commands.getChannelPermissions();
    
    // V2 (New)
    // All commands now return the data directly
    const {code} = await sdk.commands.authorize();
    const {permissions} = await discordSdk.commands.getChannelPermissions();
  7. Perform test purchases via Developer Shelf

    main

    You can test the IAP workflow without real charges by using the Developer Shelf:

    1. Launch your application via the Developer Shelf.
    2. Call RPCCommands.START_PURCHASE from your application code.
    3. Complete the purchase flow modal (a valid payment source is required, but you will not be charged).
    4. Verify that a new Entitlement is created with type: 4 (TEST_MODE_PURCHASE).
  8. Update RPC commands and TypeScript types via JSON schema

    main

    The RPC commands and TypeScript types in this SDK are derived from a JSON schema generated by Discord. If you need to update these definitions to match the latest Discord specifications, follow these steps:

    1. Generate the RPC schema from Discord: Ensure you have executed the clyde gen rpc command within the Discord environment.
    2. Sync the schema to the repository: From the root of the embedded-app-sdk repository, run the npm run sync command, providing the path to the generated schema file using the --path flag.
    3. Commit changes: Once the sync is complete, commit the updated files to your repository.
  9. Filter Entitlements for valid purchases

    main

    When retrieving entitlements via the Discord API, you must filter the results to ensure you are only processing valid, unconsumed purchases. Specifically, you should exclude:

    1. Entitlements where consumed is true.
    2. Entitlements with a type of EntitlementTypes.TEST_MODE_PURCHASE (value 4). These are test purchases made via the Developer Shelf and should only be used in development environments.

    To fetch entitlements, make a GET request to https://discord.com/api/applications/${applicationId}/entitlements using your bot token for authorization.

    // Entitlement shape
    interface Entitlement {
      user_id: string;
      sku_id: string;
      application_id: string;
      id: string;
      type: number;
      consumed: boolean;
    };
    
    // bot token needed for http authorization
    const BOT_TOKEN = 'your_bot_token';
    
    // enum for entitlement.type indicating a developer shelf purchase
    const TEST_MODE_PURCHASE = 4;
    
    // SKUs we want to check entitlements for
    const SKU_IDS = ['1234567890', '2345678901'];
    
    const entitlementsQueryParams = `?user_id=${userId}&sku_ids=${SKU_IDS.join(',')}`;
    const entitlementsResponse = await fetch(
      `https://discord.com/api/applications/${applicationId}/entitlements${entitlementsQueryParams}`,
      {
        method: 'GET',
        headers: {
          'Authorization': `Bot ${BOT_TOKEN}`,
        },
      }
    );
    const entitlementsJSON = await entitlementsResponse.json<[Entitlement]>();
    const filteredEntitlements = entitlementsJSON
      .filter(ent => !ent.consumed && ent.type !== TEST_MODE_PURCHASE);
  10. Step-by-step guide for V2 migration

    main

    To migrate your activity from v1 to v2, follow these steps:

    1. Upgrade dependencies: Update your package.json to version 2.0.0 or higher and run your package manager's install command (e.g., yarn install or npm install).
    2. Identify errors: Run the TypeScript compiler (tsc) and observe the errors. Most errors will stem from the changed command return shapes.
    3. Fix TypeScript errors:
      • For commands that previously returned a payload, remove the .data access (e.g., change response.data to response).
      • Adjust deconstruction patterns to target the data object directly.
    4. Audit usage: Perform a manual audit of command usage to catch any edge cases that TypeScript might have missed.
    5. Test: Perform basic functional testing of key activity features.
    6. Verify: Ensure all command interactions align with the new standardized shapes.
  11. Understand the READY event

    main

    The READY event is emitted by Discord's RPC server immediately after the Embedded App SDK is initialized. It is typically only emitted once. This event provides essential context for your application, including:

    • The RPC server version (v)
    • Discord client configuration (config), which includes the api_endpoint and environment
    • Basic user information (user), such as id, username, discriminator, and avatar.

    Supported Platforms: Web, iOS, Android.