/** * Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. All Rights Reserved. * SPDX-license-identifier: BSD-3-Clause * * 火山引擎 RTC AccessToken 生成器 * 来源:https://github.com/volcengine/rtc-aigc-demo/blob/main/Server/token.js */ var crypto = require('crypto'); var randomInt = Math.floor(Math.random() * 0xFFFFFFFF); const VERSION = "001"; const VERSION_LENGTH = 3; const APP_ID_LENGTH = 24; privileges = { PrivPublishStream: 0, // not exported, do not use directly privPublishAudioStream: 1, privPublishVideoStream: 2, privPublishDataStream: 3, PrivSubscribeStream: 4, }; module.exports.privileges = privileges; // Initializes token struct by required parameters. var AccessToken = function (appID, appKey, roomID, userID) { let token = this; this.appID = appID; this.appKey = appKey; this.roomID = roomID; this.userID = userID; this.issuedAt = Math.floor(new Date() / 1000); this.nonce = randomInt; this.expireAt = 0; this.privileges = {}; // AddPrivilege adds permission for token with an expiration. this.addPrivilege = function (privilege, expireTimestamp) { if (token.privileges === undefined) { token.privileges = {} } token.privileges[privilege] = expireTimestamp; if (privilege === privileges.PrivPublishStream) { token.privileges[privileges.privPublishVideoStream] = expireTimestamp; token.privileges[privileges.privPublishAudioStream] = expireTimestamp; token.privileges[privileges.privPublishDataStream] = expireTimestamp; } }; // ExpireTime sets token expire time, won't expire by default. // The token will be invalid after expireTime no matter what privilege's expireTime is. this.expireTime = function (expireTimestamp) { token.expireAt = expireTimestamp; }; this.packMsg = function () { var bufM = new ByteBuf(); bufM.putUint32(token.nonce); bufM.putUint32(token.issuedAt); bufM.putUint32(token.expireAt); bufM.putString(token.roomID); bufM.putString(token.userID); bufM.putTreeMapUInt32(token.privileges); return bufM.pack() }; // Serialize generates the token string this.serialize = function () { var bytesM = this.packMsg(); var signature = encodeHMac(token.appKey, bytesM); var content = new ByteBuf().putBytes(bytesM).putBytes(signature).pack(); return (VERSION + token.appID + content.toString('base64')); }; // Verify checks if this token valid, called by server side. this.verify = function (key) { if (token.expireAt > 0 && Math.floor(new Date() / 1000) > token.expireAt) { return false } token.appKey = key; return encodeHMac(token.appKey, this.packMsg()).toString() === token.signature; } }; module.exports.version = VERSION; module.exports.AccessToken = AccessToken; var encodeHMac = function (key, message) { return crypto.createHmac('sha256', key).update(message).digest(); }; var ByteBuf = function () { var that = { buffer: Buffer.alloc(1024) , position: 0 }; that.pack = function () { var out = Buffer.alloc(that.position); that.buffer.copy(out, 0, 0, out.length); return out; }; that.putUint16 = function (v) { that.buffer.writeUInt16LE(v, that.position); that.position += 2; return that; }; that.putUint32 = function (v) { that.buffer.writeUInt32LE(v, that.position); that.position += 4; return that; }; that.putBytes = function (bytes) { that.putUint16(bytes.length); bytes.copy(that.buffer, that.position); that.position += bytes.length; return that; }; that.putString = function (str) { return that.putBytes(Buffer.from(str)); }; that.putTreeMap = function (map) { if (!map) { that.putUint16(0); return that; } that.putUint16(Object.keys(map).length); for (var key in map) { that.putUint16(key); that.putString(map[key]); } return that; }; that.putTreeMapUInt32 = function (map) { if (!map) { that.putUint16(0); return that; } that.putUint16(Object.keys(map).length); for (var key in map) { that.putUint16(key); that.putUint32(map[key]); } return that; }; return that; };