// 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();
});