Skip to content

Instantly share code, notes, and snippets.

@xurshid29
Last active October 21, 2022 18:27
Show Gist options
  • Save xurshid29/a957039f57e2cf7021e0fba4c880ef43 to your computer and use it in GitHub Desktop.
Save xurshid29/a957039f57e2cf7021e0fba4c880ef43 to your computer and use it in GitHub Desktop.
TMP: Agora chat access-token2 for nodejs
@xurshid29
Copy link
Author

xurshid29 commented Oct 21, 2022

This is the TEMPORARY solution for the AccessToken2:

  1. Copy the source-code for nodejs from the Tools (https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) repo
  2. Move the contents of the downloaded folder to you project dir, for ex:
    image
  3. Add it to your package.json manually, like this, and run npm install:
    image
  4. And use it. Below is a sample implementation for the nestjs (inspired by PHP version - https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/php/src):
import { Injectable } from "@nestjs/common";
import { AxiosInstance } from "axios";
import { HttpService } from "@nestjs/axios";
import { ConfigService } from "@nestjs/config";
import { AccessToken2, ServiceChat } from "agora-access-token";

@Injectable()
export class AgoraService {
  private axios: AxiosInstance;

  constructor(
    private readonly http: HttpService,
    private readonly configService: ConfigService,
  ) {
    // here, AGORA_CHAT_APP_KEY is obtained from agora console and it looks something like this - 12312312#12312312
    const [orgName, appName] = (
      configService.get("AGORA_CHAT_APP_KEY") as string
    ).split("#");

    const axiosClient = http.axiosRef as AxiosInstance;

    // here, AGORA_CHAT_REST_API_DOMAIN is obtained from agora console and it looks somethign like this - a00.chat.agora.io
    axiosClient.defaults.baseURL = `https://${this.configService.get(
      "AGORA_CHAT_REST_API_DOMAIN",
    )}/${orgName}/${appName}`;

    this.axios = axiosClient;
  }

  async buildChatUserToken(userId: number): Promise<string> {
    const agoraUid = await this.getAgoraUid(userId);

    if (agoraUid != null) {
      return this.buildToken(1, agoraUid);
    }

    return "";
  }

  private buildToken(privilege: number, uid?: string): string {
    const expireTime = Number(
      this.configService.get("AGORA_CHAT_TOKEN_EXPIRE"),
    );
    const currentTime = Math.floor(Date.now() / 1000);
    const privilegedExpireTime = currentTime + expireTime;

    const accessToken = new AccessToken2(
      this.configService.get("AGORA_APP_ID"),
      this.configService.get("AGORA_APP_CERTIFICATE"),
      currentTime,
      expireTime,
    );

    const service = new ServiceChat(uid);
    service.add_privilege(privilege, privilegedExpireTime);
    accessToken.add_service(service);

    return accessToken.build();
  }

  private async getAgoraUid(id: number): Promise<string> {
    const accessToken = await this.getAccessToken();

    const {
      data: { entities },
      status,
    } = await this.axios.get<{
      entities: {
        uuid: string;
      }[];
    }>(`/users/${id}`, {
      validateStatus: () => true,
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
    });

    if (status === 404) {
      const {
        data: { entities },
      } = await this.axios.post<{ entities: { uid: string }[] }>(
        `/users`,
        {
          username: id,
          password: "123",
        },
        {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        },
      );

      return entities[0].uid;
    }

    if (entities != null && entities.length > 0) {
      return entities[0].uuid;
    }

    return null;
  }

  private async getAccessToken(): Promise<string> {
    const appToken = this.buildToken(2);

    const {
      data: { access_token },
    } = await this.axios.post<{ access_token: string }>(
      `/token`,
      {
        grant_type: "agora",
      },
      {
        headers: {
          Authorization: `Bearer ${appToken}`,
        },
      },
    );

    return access_token;
  }
}

For Typescript you may also want to declare types for the AccessToken2 and ServiceChat:

declare module "agora-access-token" {
  export class ServiceChat {
    constructor(public user_id?: string) {}
    add_privilege(privilege: number, expire: number): void;
  }

  export class AccessToken2 {
    constructor(
      public appId: string,
      public appCertificate: string,
      public issueTs?: number,
      public expire: number,
    ) {}

    add_service(service: ServiceChat): void;
    build(): string;
    from_string(origin_token: string): void;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment