Web SDK integration

更新时间:
复制 MD 格式

Interactive Messaging enhances communication and user engagement in live streaming. Its rich, easy-to-integrate SDK lets you add features such as comments, bullet screens, and likes to your live streaming application. This topic describes how to integrate the Interactive Messaging SDK on the web.

Prerequisites

Before you integrate the client, make sure you have completed server-side integration and provided a client-accessible API endpoint that returns an authentication token. For instructions, see Server-side Integration.

Browser support

The Web SDK relies on WebRTC. The following browser versions are supported:

  • Chrome 63+

  • Firefox 62+

  • Opera 50+

  • Edge 79+

  • Safari 11+

Integrate the SDK

Include the SDK by adding a <script> tag.

<script src="https://g.alicdn.com/apsara-media-box/imp-interaction/1.9.0/alivc-im.iife.js"></script>

Download the type definition file to add type definitions for the SDK and improve your development experience. Rename the downloaded alivc-im.iife.d.bin file to alivc-im.iife.d.zip, and then unzip it to obtain alivc-im.iife.d.ts.

Use the SDK

Use the SDK by following these steps in order:

  1. Initialization

  2. Login

  3. Related operations

  4. Logout

  5. Uninitialization

  6. Other

The core SDK functions are divided into group operations and message operations.

SDK operations

  • Group operations

    • Create a group (requires login as an administrator)

    • Close a group (only for the group owner or a group administrator)

    • Join a group

    • Leave a group

    • Query group information

    • Modify group information (only for the group owner or a group administrator)

    • List recent group members

    • List all group members (only for the group owner or a group administrator)

    • Mute a group (only for the group owner or a group administrator)

    • Unmute a group (only for the group owner or a group administrator)

    • Mute a user in a group (only for the group owner or a group administrator)

    • Unmute a user in a group (only for the group owner or a group administrator)

    • List muted users in a group (only for the group owner or a group administrator)

  • Message operations

    • Send a unicast message

    • Send a group message

    • List recent group messages

    • List all group messages (only for the group owner or a group administrator)

    • Delete or recall a group message

    • Query historical messages

Notes

The following rules apply to ID formats:

application ID: A string of up to 64 characters. It can contain uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), and hyphens (-). Other characters are not allowed.

user ID: A string of up to 64 characters. It can contain uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), and hyphens (-). Other characters are not allowed.

group ID: A string of up to 64 characters. It can contain uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), and hyphens (-). Other characters are not allowed.

Initialization

Initialize the SDK before use. We recommend that you perform initialization at the main entry point of your application.

Sample code

const { ImEngine, ImLogLevel } = AliVCInteraction;

// Get the engine singleton.
const engine = ImEngine.createEngine();

try {
  await engine.init({
    deviceId: "deviceId", //[Optional]
    appId: "appId", //[Required] If left empty, initialization fails with error code -2. You must replace APP_ID in the sample code with the AppId of your application. Otherwise, the service is unavailable.
    appSign: "appSign", //[Required] If left empty, initialization fails with error code -2. You must replace APP_SIGN in the sample code with the AppSign of your application. Otherwise, the service is unavailable.
    logLevel: ImLogLevel.ERROR, //[Optional] Specifies the minimum level of logs that can be output. Default value: ImLogLevel.DEBUG. To disable logging, set this parameter to ImLogLevel.NONE.
  });
  // Make sure the asynchronous init operation is successful before you proceed with operations like login.

  // Initialization is successful. Listen for events.
  // Handle callback events from AliVCIMEngineListenerProtocol.
  engine.on("connecting", () => {
    console.log("connecting");
  });
  
  engine.on("connectfailed", (err) => {
    console.log(`connect failed: ${err.message}`);
  });
  
  engine.on("connectsuccess", () => {
    console.log("connect success");
  });
  
  engine.on("disconnect", (code) => {
    // Disconnection reason. 1: User logged out. 2: User was kicked. 3: Connection timeout. 4: Logged in on another device.
    console.log(`disconnect: ${code}`);
  });

  engine.on('linkstate', (data) => {
    // The link state changed. state: 0 (Disconnected), 1 (Connecting), 2 (Connected), 3 (Disconnected).
    console.log(`previousState: ${data.previousState}, currentState: ${data.currentState}`);
  });

  engine.on('reconnectsuccess', (groupInfos) => {
    // Reconnection is successful. Information about the groups you are in is returned.
    console.log(groupInfos);
  });
  
  engine.on("tokenexpired", async (cb) => {
    console.log("token expired");
    // Add your code here to obtain new login information.
    const auth = await getLoginAuth();
    cb(null, {
      timestamp: 22123123, // The timestamp value returned by the server.
      nonce: 'nonce',      // The nonce value returned by the server.
      role: 'admin',       // The user role. Set to an empty string if not needed.
      token: 'xxx'         // The token value returned by the server.
    });
  });
  
} catch (error) {
  // Initialization error codes:
  // A non-zero return value indicates that initialization failed. 1001: Repeated initialization. 1002: Failed to create the underlying engine. -1: Repeated initialization of the underlying engine. -2: Invalid initialization configuration.
  console.log(`Init Fail: code:${error.code}, message: ${error.msg}`);
}

Login

Login requires authentication credentials. Make sure you have met the prerequisites and obtained the timestamp, nonce, and token from your server.

Sample code

try {  
     // Make sure the asynchronous init operation is successful before you run this code.
    await engine.login({
      user: {
        userId: 'abc',       // The user ID for the current application login.
        userExtension: '{}', // User extension information, such as an avatar or nickname, encapsulated as a JSON string.
      },
      userAuth: {
        timestamp: 22123123, // The timestamp value returned by the server.
        nonce: 'nonce',      // The nonce value returned by the server.
        role: 'admin',       // The user role. Set to an empty string if not needed.
        token: 'xxx'         // The token value returned by the server.
      },
    });
 } catch (error) {
    // 304: Already logged in. 400: Invalid parameters. 403: Authentication failed during login.
    console.log(`login Fail: code:${error.code}, message: ${error.msg}`);
}

Group operations

Make sure the asynchronous login operation completes successfully before you perform these operations.

Get the group manager

// Make sure the engine is initialized. Otherwise, a null value is returned.
const groupManager = engine.getGroupManager();

Add and remove group listeners

// Add group operation event listeners at the appropriate time, for example, after entering a room and completing login.
groupManager.on('exit', (groupId, reason) => {
  // reason - 1: The group was disbanded. 2: You were kicked out.
  console.log(`group ${groupId} close, reason: ${reason}`);
});

// Deprecated since v1.4.1. Listen for the `memberdatachange` event instead.
groupManager.on('memberchange', (groupId, memberCount, joinUsers, leaveUsers) => {
  // A user joined or left the group.
  console.log(`group ${groupId} member change, memberCount: ${memberCount}, joinUsers: ${joinUsers.map(u => u.userId).join(',')}, leaveUsers: ${leaveUsers.map(u => u.userId).join('')}`);
});
/**
 * A new group member change event was added in v1.4.1 that returns an object.
 * @param data The group member change data object.
 * @param data.groupId The group ID.
 * @param data.onlineCount The current number of online users in the group.
 * @param data.pv The cumulative page views from users joining the group.
 * @param data.isBigGroup Whether the group is a super large group.
 * @param data.joinUsers The users who joined.
 * @param data.leaveUsers The users who left.
 */
groupManager.on('memberdatachange', (data) => {
  const { groupId, onlineCount, pv, isBigGroup, joinUsers, leaveUsers } = data;
  console.log(`group ${groupId} member change, onlineCount: ${onlineCount}, pv: ${pv}, joinUsers: ${joinUsers.map(u => u.userId).join(',')}, leaveUsers: ${leaveUsers.map(u => u.userId).join('')}`);
});

groupManager.on('mutechange', (groupId, status) => {
  // The mute status of the group has changed.
  console.log(`group ${groupId} mute change`);
});

groupManager.on('infochange', (groupId, info) => {
  // The group information has changed.
  console.log(`group ${groupId} info change`);
});

// To remove a listener for a specific event, use the off method.
groupManager.off('infochange');

// To remove all event listeners, use the removeAllListeners method.
groupManager.removeAllListeners();

Create a group

Only administrators can call this method.

Note

You can create up to 5,000 groups. Closed groups do not count toward this limit. To free up resources, close groups that are no longer needed.

await groupManager.createGroup({
  groupId: '',       // The group ID. If empty, the system returns a unique ID after creating the group.
  groupName: 'xxx',  // The group name. This must be set, or the call will fail.
  groupMeta: 'xxx'   // Group extension information. If there are multiple fields, consider encapsulating them into a JSON string.
});

Close a group

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

// The parameter is groupId.
await groupManager.closeGroup('xxx');

Join a group

// The parameter is groupId.
await groupManager.joinGroup('xxx');

Leave a group

// The parameter is groupId.
await groupManager.leaveGroup('xxx');

Query group information

// The parameter is groupId.
const groupInfo = await groupManager.queryGroup('xxx');

Modify group information

You can use this method to modify group extension information and set administrators. Only the group owner or a group administrator can call this method. Otherwise, the call fails.

await groupManager.modifyGroup({
  groupId: 'xxx',  // The group ID.
  forceUpdateAdmins: true, // Specifies whether to forcibly modify the group administrators.
  admins: ['xxx'], // A list of group administrator IDs. To clear the list, pass an empty array and set forceUpdateAdmins to true.
  forceUpdateGroupMeta: true, // Specifies whether to forcibly modify the group extension information.
  groupMeta: 'xxx' // Group extension information (Meta). To clear it, pass an empty string and set forceUpdateGroupMeta to true.
});

List recent group members

/**
 * The parameter is groupId.
 * This API is not available for super large groups. You must check if the current group is a super large group.
 * 1. The `ImGroupInfo.isBigGroup` property returned after joining a group is `true`.
 * 2. A regular group can become a super large group as more members join.
 * 3. The `ImMemberChangeData.isBigGroup` property from the `groupListener.onMemberChange()` listener is `true`.
 */
const recentGroupUserInfo = groupManager.listRecentGroupUser(groupId);

List all group members

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

/**
 * This API is not available for super large groups. You must check if the current group is a super large group.
 * 1. The `ImGroupInfo.isBigGroup` property returned after joining a group is `true`.
 * 2. A regular group can become a super large group as more members join.
 * 3. The `ImMemberChangeData.isBigGroup` property from the `groupListener.onMemberChange()` listener is `true`.
 */
const groupUserInfo = groupManager.listGroupUser({
  groupId: 'xxx',    // The group ID.
  nextpagetoken: 12, // If not provided, it indicates the first page. The server returns this token during iteration. Your client should include it when fetching the next page.
  pageSize: 10,      // The maximum value is 50.
  sortType: ImSortType.ASC // The sort order. ASC: oldest members first; DESC: newest members first.
});

Mute a group

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

// The parameter is groupId.
await groupManager.muteAll('xxx');

Unmute a group

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

// The parameter is groupId.
await groupManager.cancelMuteAll('xxx');

Mute a user in a group

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

await groupManager.muteUser({
  groupId: 'xxx',    // The group ID.
  userList: ['xxx']  // A list of user IDs to mute.
});

Unmute a user in a group

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

await groupManager.cancelMuteUser({
  groupId: 'xxx',    // The group ID.
  userList: ['xxx']  // A list of user IDs to unmute.
});

List muted users

Only the group owner or a group administrator can call this method. Otherwise, the call fails.

// The parameter is groupId.
const muteUsersInfo = await groupManager.listMuteUsers('xxx');

Message operations

Get the message manager

// Make sure the engine is initialized. Otherwise, a null value is returned.
const messageManager = await engine.getMessageManager();

Add and remove message listeners

// A unicast message is received from another user.
messageManager.on("recvc2cmessage", (msg) => {
  console.log('recvc2cmessage', msg);
});

// A group message is received.
messageManager.on("recvgroupmessage", (msg, groupId) => {
  console.log('recvgroupmessage', msg, groupId);
});

// A group message is deleted.
messageManager.on('deletegroupmessage', (msgId, groupId) => {
  console.log(`group ${groupId} delete message ${msgId}`)
});

// To remove a listener for a specific event, use the off method.
messageManager.off('recvgroupmessage');

// To remove all event listeners, use the removeAllListeners method.
messageManager.removeAllListeners();

Send a unicast message

try {
  const messageId = await messageManager.sendC2cMessage({
    receiverId: 'xxx',     // The receiver's ID.
    data: 'xxx',           // The message content. For structured data, consider using a JSON string.
    type: 88888,           // The custom message type. The value must be greater than 10000.
    skipAudit: false,      // Optional (default: false). Specifies whether to skip the security review. true: The message bypasses the Alibaba Cloud security review service. false: The message is reviewed by the Alibaba Cloud security review service. If it fails the review, it is not sent.
    level: ImMessageLevel.NORMAL // Sets the message level. Default is NORMAL. For details, see the "Message Level Throttling" documentation.
  });
  console.log('send success, messageId: ', messageId);
} catch (error) {
  // If error.code is 424, the recipient is offline. Since offline messages are not supported, the recipient must be online. Consider resending the message later.
  console.log('send fail', error);
}

Use level: ImMessageLevel.NORMAL to set the message level. The default level is NORMAL. For more information, see more information.

Send a group message

// Make sure you have joined the group before sending a group message.
try {
  const messageId = await messageManager.sendGroupMessage({
    groupId: 'xxx',       // The group ID.
    data: 'xxx',          // The message content. For structured data, consider using a JSON string.
    type: 88888,          // The custom message type. The value must be greater than 10000.
    skipAudit: false,     // Optional (default: false). Specifies whether to skip the security review. true: The message bypasses the Alibaba Cloud security review service. false: The message is reviewed by the Alibaba Cloud security review service. If it fails the review, it is not sent.
    skipMuteCheck: false, // Optional (default: false). Specifies whether to skip the mute check. true: A muted user can still send messages. false: A muted user cannot send messages.
    level: ImMessageLevel.NORMAL, // Sets the message level. Default is NORMAL. For details, see the "Message Level Throttling" documentation.
    sendMessageOption:{
      isStorageEnable:false,// Specifies whether to store the message. Default is false. If true, the sent message is stored in the database and is returned when you query historical messages or message lists.
      isCacheEnable:false// Specifies whether to cache the message. Default is false. If true, the sent message is cached in memory (Note: Only the last 50 messages are cached) and is returned when you query the recent message list.
    },
    repeatCount: 1        // Optional (default: 1). The number of times this message is repeated. Messages with identical content can be aggregated and sent once by using this field.
  });
  console.log('send success, messageId: ', messageId);
} catch (error) {
  // If error.code is 425, you have not joined the group.
  console.log('send fail', error);
}

Set the message level by using level: ImMessageLevel.NORMAL. The default level is NORMAL. For more information, see More information.

Delete or recall a group message

// Make sure you have joined the group before deleting or recalling a group message.
await messageManager.deleteMessage({
  groupId: 'xxx',       // The group ID.
  messageId: 'xxx',     // The ID of the message to delete.
});

List recent group messages

// Make sure you have joined the group before listing group messages.
const messagesInfo = await messageManager.listRecentMessage({
  groupId: 'xxx',     // The group ID.
})
Note

After you join a group, you can call this API to retrieve the last 50 messages. All types of users can call this method.

List all group messages

Only the group owner or a group administrator can call this method. Otherwise, the call fails. Regular users should use the listRecentMessage API to retrieve the last 50 messages.

// ImSortType.ASC for ascending order, ImSortType.DESC for descending order.
// Make sure you have joined the group before listing all group messages.
const { ImSortType } = window.AliVCInteraction;

// You must first call the engine.getMessageManager method to get the messageManager object.
const messageList = await messageManager.listMessage({
  groupId: 'xxx',    // The group ID.
  type: 88888,       // The custom message type. The value must be greater than 10000.
  nextPageToken: 12, // If not provided, it indicates the first page. The server returns a token for the next page during iteration. Your client should include it when fetching the next page.
  pageSize: 10,      // Default: 10. Maximum: 30.
  sortType: ImSortType.ASC // The sort order. Default is ascending by time.
});

Query historical messages

// ImSortType.ASC for ascending order, ImSortType.DESC for descending order.
const { ImSortType } = window.AliVCInteraction;

// You must first call the engine.getMessageManager method to get the messageManager object.
const messageList = messageManager.listHistoryMessage({
  groupId: 'xxx',    // The group ID.
  type: 88888,       // The message type. Custom message types must be greater than 10000.
  nextPageToken: 12, // Optional. If not provided, it indicates the first page. The server returns a token for the next page during iteration. Your client should include it when fetching the next page.
  pageSize: 10,      // Default: 10. Maximum: 30.
  sortType: ImSortType.ASC, // The sort order. Default is ascending by time.
  beginTime: 0,      // Optional (default: 0). The start time as a timestamp in seconds.
  endTime: 0         // Optional (default: 0). The end time as a timestamp in seconds.
});

Logout

await engine.logout();

Deinitialization

If you do not plan to log in again, uninitialize the SDK to release its resources.

engine.unInit()

Other

The following helper APIs are useful for specific scenarios.

// Checks if the SDK is initialized. Use this to determine if the SDK has been instantiated.
ImEngine.isInitialized();

// Checks if the user is logged in (returns false during login). A login is required for operations like joining a group or logging out.
engine.isLogin();

// Checks if the user is logged out (returns false during login). The user must be logged out before performing a new login.
engine.isLogout();

Quick start

Prerequisites

To run the demo, you need an HTTP server in your development environment. If the http-server npm package is not installed, run npm install --global http-server to install it globally.

Step 1: Create a directory

Create a demo folder with the following directory structure, containing quick.html and quick.js.

- demo
  - quick.html
  - quick.js

Step 2: Edit quick.html

Copy the following code into quick.html and save the file.

Sample code

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Interactive Messaging quick start</title>
    <link rel="stylesheet" href="https://g.alicdn.com/code/lib/bootstrap/5.3.0/css/bootstrap.min.css" />
  </head>
  <body class="container">
    <h1 class="mt-2">Interactive Messaging quick start</h1>

    <div class="toast-container position-fixed top-0 end-0 p-3">
      <div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">Login Message</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="loginToastBody"></div>
      </div>
    </div>

    <div class="row mt-3">
      <div class="col-6">
        <form id="loginForm">
          <div class="form-group mb-2">
            <label for="userId" class="form-label">User ID</label>
            <input class="form-control form-control-sm" id="userId" placeholder="Enter letters or numbers" />
          </div>
          <div class="form-group mb-2">
            <label for="groupId" class="form-label">Group ID</label>
            <input class="form-control form-control-sm" id="groupId" placeholder="Before you join a group, confirm that it already exists. If not, create one first." />
          </div>
          <div class="mb-2">
            <button id="loginBtn" type="button" class="btn btn-primary btn-sm">Login</button>
            <button id="joinBtn" type="button" class="btn btn-primary btn-sm">Join Group</button>
            <button id="createBtn" type="button" class="btn btn-primary btn-sm">Create Group</button>
            <button id="leaveBtn" type="button" class="btn btn-secondary btn-sm" disabled>Leave Group</button>
            <button id="logoutBtn" type="button" class="btn btn-secondary btn-sm" disabled>Logout</button>
          </div>
          <p class="mb-2">If the group ID already exists, you can log in and join with one click.</p>
          <div class="mb-2">
            <button id="oneLoginBtn" type="button" class="btn btn-primary btn-sm">One-click Login + Join Group</button>
            <button id="oneLogoutBtn" type="button" class="btn btn-secondary btn-sm" disabled>One-click Leave Group + Logout</button>
          </div>
        </form>

        <form id="msgForm" action="#" class="mt-4">
          <div class="form-group mb-2">
            <label for="msgText" class="form-label">Message</label>
            <input class="form-control form-control-sm" id="msgText" />
          </div>
          <div class="mb-2">
            <button id="sendBtn" type="button" class="btn btn-primary btn-sm" disabled>Send</button>
          </div>
        </form>
      </div>
      <div class="col-6">
        <h5>
          Message Display
          <button id="clearBtn" type="button" class="btn btn-secondary btn-sm float-end">Clear</button>
        </h5>
        
        <div id="msgList" class="mt-4"></div>
      </div>
    </div>

    <script src="https://g.alicdn.com/code/lib/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://g.alicdn.com/code/lib/bootstrap/5.3.0/js/bootstrap.min.js"></script>
    <script crossorigin="anonymous" src="https://g.alicdn.com/apsara-media-box/imp-interaction/1.4.1/alivc-im.iife.js"></script>
    <script src="./quick.js"></script>
  </body>
</html>

Step 3: Edit quick.js

Copy the following code into quick.js and replace the AppId, AppKey, and AppSign placeholders with your actual application credentials.

Sample code

// Note: This authentication method is for demonstration purposes only. In a production environment, never expose your AppKey or AppSign in client-side code.
const AppId = '';
const AppKey = '';
const AppSign = '';

const { ImEngine, ImLogLevel, ImMessageLevel } = window.AliVCInteraction;
// Get the engine singleton.
const engine = ImEngine.createEngine();

let groupManager;
let messageManager;
let joinedGroupId;

const sha256 = async (message) => {
  // Convert the message string to an ArrayBuffer.
  const encoder = new TextEncoder();
  const data = encoder.encode(message);

  // Calculate the hash using the subtle crypto API.
  const result = await crypto.subtle.digest('SHA-256', data).then((buffer) => {
    // Convert the ArrayBuffer to a hexadecimal string.
    let hash = Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('');
    return hash;
  });

  return result;
};

const getLoginAuth = async (userId, role) => {
  const nonce = 'AK_4';

  const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;

  const pendingShaStr = `${AppId}${AppKey}${userId}${nonce}${timestamp}${role}`;
  const appToken = await sha256(pendingShaStr);

  return {
    nonce,
    timestamp,
    token: appToken,
    role,
  };
};

function showToast(baseId, message) {
  $(`#${baseId}Body`).text(message);
  const toast = new bootstrap.Toast($(`#${baseId}`));

  toast.show();
}

function showMessage(text) {
  $('#msgList').append(`<div class="mb-2">${text}</div>`);
}

function listenEngineEvents() {
  // Handle callback events from AliVCIMEngineListenerProtocol.
  engine.on("connecting", () => {
    console.log("connecting");
  });
  
  engine.on("connectfailed", (err) => {
    console.log(`connect failed: ${err.message}`);
  });
  
  engine.on("connectsuccess", () => {
    console.log("connect success");
  });
  
  engine.on("disconnect", (code) => {
    console.log(`disconnect: ${code}`);
  });
  
  engine.on("tokenexpired", async (cb) => {
    console.log("token expired");
    // Add your code here to obtain a new login token.
    // const auth = await getLoginAuth(userId, role);
    // cb(null, auth);
  });
}

function listenGroupEvents() {
  if (!groupManager) {
    return;
  }
  // Add group operation event listeners at the appropriate time, for example, after entering a room and completing login.
  groupManager.on('exit', (groupId, reason) => {
    // Left the group.
    showMessage(`group ${groupId} close, reason: ${reason}`);
  })
  groupManager.on('memberchange', (groupId, memberCount, joinUsers, leaveUsers) => {
    // A user joined or left the group.
    showMessage(`group ${groupId} member change, memberCount: ${memberCount}, joinUsers: ${joinUsers.map(u => u.userId).join(',')}, leaveUsers: ${leaveUsers.map(u => u.userId).join('')}`);
  })
  groupManager.on('mutechange', (groupId, status) => {
    // The mute status of the group has changed.
    showMessage(`group ${groupId} mute change`);
  })
  groupManager.on('infochange', (groupId, info) => {
    // The group information has changed.
    showMessage(`group ${groupId} info change`);
  })
}

function listenMessageEvents() {
  if (!messageManager) {
    return;
  }
  // A group message is received.
  messageManager.on("recvgroupmessage", (msgData, groupId) => {
    console.log('recvgroupmessage', msgData, groupId);
    showMessage(`receive group: ${msgData.groupId}, type: ${msgData.type}, data: ${msgData.data}`);
  });
}

async function login(userId) {
  // Initialize first. Remember to use await.
  await engine.init({
    deviceId: "xxxx",    // The device ID. This is optional.
    appId: AppId,     // Copy this from the console after creating an application.
    appSign: AppSign, // Copy this from the console after creating an application.
    logLevel: ImLogLevel.ERROR,  // The log level. Use ImLogLevel.DEBUG for debugging.
  });
  // If initialization is successful, listen for events.
  listenEngineEvents();

  const role = 'admin'; // The user role. Set to an empty string if not needed.
  // Get login information.
  const authData = await getLoginAuth(userId, role);
  // Log in after successful initialization. Remember to use await.
  await engine.login({
    user: {
      userId,       // The user ID for the current application login.
      userExtension: '{}', // User extension information, such as an avatar or nickname, encapsulated as a JSON string.
    },
    userAuth: {
      timestamp: authData.timestamp, // The timestamp value returned by the server.
      nonce: authData.nonce,      // The nonce value returned by the server.
      role: authData.role,       // The user role. Set to an empty string if not needed.
      token: authData.token,        // The token value returned by the server.
    },
  });

  // Make sure the engine is initialized. Otherwise, a null value is returned.
  groupManager = engine.getGroupManager();
  messageManager = engine.getMessageManager();
}

async function logout() {
  await engine.logout();
  engine.unInit();
  groupManager = undefined;
  messageManager = undefined;
}

async function joinGroup(groupId) {
  if (!groupManager) {
    return;
  }
  await groupManager.joinGroup(groupId);
  joinedGroupId = groupId;
  listenGroupEvents();
  listenMessageEvents();
}

async function leaveGroup() {
  if (!groupManager || !joinedGroupId) {
    return;
  }
  await groupManager.leaveGroup(joinedGroupId);
  groupManager.removeAllListeners();
  messageManager.removeAllListeners();
}

$('#loginBtn').click(() => {
  const userId = $('#userId').val();
  if (!userId) {
    return;
  }
  login(userId)
    .then(() => {
      console.log('Initialization and login successful');
      showToast('loginToast', 'Initialization and login successful');
      $('#loginBtn').prop('disabled', true);
      $('#logoutBtn').prop('disabled', false);
    })
    .catch((err) => {
      console.log('Initialization and login failed', err.code, err.msg);
    });
});

$('#oneLoginBtn').click(async () => {
  const userId = $('#userId').val();
  const groupId = $('#groupId').val();
  if (!userId || !groupId) {
    return;
  }
  try {
    await login(userId);
    showToast('loginToast', 'Initialization and login successful');
    $('#loginBtn').prop('disabled', true);
    $('#logoutBtn').prop('disabled', false);

    await joinGroup(groupId);
    showMessage(`Successfully joined group ${groupId}`);
    $('#joinBtn').prop('disabled', true);
    $('#leaveBtn').prop('disabled', false);
    $('#sendBtn').prop('disabled', false);
    $('#oneLoginBtn').prop('disabled', true);
    $('#oneLogoutBtn').prop('disabled', false);
  } catch (error) {
    console.log('One-click login and join group:', error.code, error.msg);
  }
});

$('#joinBtn').click(() => {
  const groupId = $('#groupId').val();
  if (!groupId || !groupManager) {
    return;
  }
  joinGroup(groupId)
    .then(() => {
      showMessage(`Successfully joined group ${groupId}`);
      $('#joinBtn').prop('disabled', true);
      $('#leaveBtn').prop('disabled', false);
      $('#sendBtn').prop('disabled', false);
    })
    .catch((err) => {
      console.log('Failed to join group', err.code, err.msg);
    });
});

$('#logoutBtn').click(() => {
  logout()
    .then(() => {
      console.log('Logout successful');
      showToast('loginToast', 'Logout successful');
      $('#loginBtn').prop('disabled', false);
      $('#logoutBtn').prop('disabled', true);
    })
    .catch((err) => {
      console.log('Initialization or login failed', err.code, err.msg);
    });
});

$('#leaveBtn').click(() => {
  leaveGroup()
    .then(() => {
      showMessage('Successfully left group');
      $('#leaveBtn').prop('disabled', true);
      $('#sendBtn').prop('disabled', true);
      $('#joinBtn').prop('disabled', false);
    })
    .catch((err) => {
      console.log('Failed to leave group', err.code, err.msg);
    });
});

$('#oneLogoutBtn').click(async () => {
  try {
    // Leave the group first, and proceed to logout regardless of success.
    await leaveGroup();
  } catch (error) {
    console.log(error);
  }
  try {
    await logout();
    showToast('loginToast', 'Logout successful');
    $('#loginBtn').prop('disabled', false);
    $('#logoutBtn').prop('disabled', true);
    $('#leaveBtn').prop('disabled', true);
    $('#sendBtn').prop('disabled', true);
    $('#joinBtn').prop('disabled', false);
    $('#oneLogoutBtn').prop('disabled', true);
    $('#oneLoginBtn').prop('disabled', false);
  } catch (error) {
    console.log(error);
  }
});

$('#createBtn').click(() => {
  const groupId = $('#groupId').val();
  if (!groupManager) {
    return;
  }
  groupManager.createGroup(
    {
      groupId,       // The group ID. If empty, the system returns a unique ID after creating the group.
      groupName: 'xxx',  // The group name. This must be set, or the call will fail.
      groupMeta: 'xxx'   // Group extension information. If there are multiple fields, consider encapsulating them into a JSON string.
    }
  )
  .then((res) => {
    console.log('Successfully created group', res);
  })
  .catch((err) => {
    console.log('Failed to create group', err.code, err.msg);
  });
});

$('#sendBtn').click(() => {
  const text = $('#msgText').val();
  if (!messageManager || !joinedGroupId) {
    return;
  }
  messageManager.sendGroupMessage({
    groupId: joinedGroupId,       // The group ID.
    data: text,          // The message content. For structured data, consider using a JSON string.
    type: 88888,          // The custom message type. Must be greater than 10000.
    skipAudit: false,     // Optional (default: false). Specifies whether to skip the security review. true: The message bypasses the Alibaba Cloud security review service. false: The message is reviewed by the Alibaba Cloud security review service. If it fails the review, it is not sent.
    skipMuteCheck: false, // Optional (default: false). Specifies whether to skip the mute check. true: A muted user can still send messages. false: A muted user cannot send messages.
    level: ImMessageLevel.NORMAL, // Optional (default: NORMAL). The message level. Use ImMessageLevel.HIGH for high reliability.
    noStorage: true,     // Optional (default: false). true: The message is not stored and cannot be retrieved. false: The message is stored and can be retrieved. For bullet screen scenarios in live streaming, set this to false.
    repeatCount: 1        // Optional (default: 1). The number of times this message is repeated. Messages with identical content can be aggregated and sent once by using this field.
  })
  .then((res) => {
    console.log('Group message sent successfully', res);
    $('#msgText').val('');
  })
  .catch((err) => {
    console.log('Failed to send group message', err.code, err.msg);
  });
});

$('#clearBtn').click(() => {
  $('#msgList').empty();
});

Step 4: Run the demo

  1. In the terminal, navigate to the demo folder and run http-server -p 8080 -c-1 to start an HTTP service.

  2. Open a new tab in your browser, go to localhost:8080/quick.html, enter a User ID, and click the Login button.

  3. Enter a group ID and click Join Group.

  4. Open a new tab in your browser and go to localhost:8080/quick.html. On the page, enter the same group ID as in the previous step and another user ID, and click the Login and Join Group buttons.

  5. Type a message in the input box and click Send. After sending, both users will receive the message.