使用 Angela 集成 Crisp

集成 Crisp

extension.html 中添加 Crisp 相关的 <script> 即可为 Angela 启用 Crisp。

此外,通过 Angela Access Points ,Crisp 会话可以携带更多的上下文信息。

以下是一个示例实现,将全部代码复制到 extension.html 即可启用进阶 Crisp 集成:

<script type="module">
/** @import { Observable } from "rxjs" */
/** @import { User } from "../../../../../angela/src/app/core/user/user.model" */
/** @import { Plan } from "../../../../../angela/src/app/core/plan/plan.model" */

/**
 * @typedef AngelaAccessPoints
 * @property {() => Observable} watchCommands
 * @property {(command) => object} readCommandMetadata
 * @property {() => Observable} watchEvents
 * @property {(event) => object} readEventMetadata
 * @property {() => Observable<string>} queryAuthToken
 * @property {() => Observable<User>} queryUser
 * @property {() => Observable<Plan | null>} queryUserPlan
 */

/**
 * @typedef CrispSdk
 * @property {(args: [string, string, [unknown]]) => void} push
 * @property {boolean} [adapted]
 * @see https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/dollar-crisp/
 */

/**
 * @typedef {AngelaAccessPoints & { $crisp?: CrispSdk }} Global
 */

import * as mRxJS from 'https://cdn.skypack.dev/rxjs@7.8.1';
import * as mFilesize from 'https://cdn.skypack.dev/filesize@10.0.12';
import * as mDayjs from 'https://cdn.skypack.dev/dayjs@1.11.9/esm';

/**@returns {asserts v is Global} */
function typeAssertGlobal(v) {}
/**@returns {asserts m is typeof import("rxjs")} */
function typeAssertModuleRxJS(m) {}
/**@returns {asserts m is typeof import("filesize")} */
function typeAssertModuleFilesize(m) {}
/**@returns {asserts m is { default: typeof import('dayjs/esm') }} */
function typeAssertModuleDayjs(m) {}

typeAssertGlobal(window);
const global = window;
const { queryAuthToken, queryUser, queryUserPlan } = global;
typeAssertModuleRxJS(mRxJS);
const { switchMap, combineLatest, timer, first, map } = mRxJS;
typeAssertModuleDayjs(mDayjs);
const dayjs = mDayjs.default;
typeAssertModuleFilesize(mFilesize);
const { filesize } = mFilesize;

timer(1000, 1000)
  .pipe(
    map(() => global.$crisp),
    first(Boolean),
  )
  .subscribe((crisp) => {
    adaptCrisp(crisp);
    crisp.adapted = true;
  });

/**
 * @param {CrispSdk} crisp
 */
function adaptCrisp(crisp) {
  queryAuthToken()
    .pipe(
      switchMap((token) => {
        if (!token) return [];
        const user$ = queryUser();
        const userPlan$ = queryUserPlan();
        return combineLatest([user$, userPlan$]);
      }),
    )
    .subscribe(([user, plan]) => {
      provideCrispData(crisp, user, plan);
    });
}

/**
 * @param {CrispSdk} crisp
 * @param {User} user
 * @param {?Plan} plan
 */
function provideCrispData(crisp, user, plan) {
  const formatFilesize = (/**@type {number} */ size) =>
    filesize(size, { base: 2, standard: 'jedec', spacer: '' });
  const formatDate = (/**@type {Date} */ date) =>
    dayjs(date).format('YYYY-MM-DD');

  const planExpiry = user.planExpireAt;
  const trafficTotal = user.trafficTotal;
  const trafficUsed = user.trafficUploaded + user.trafficDownloaded;

  crisp.push(['set', 'user:email', [user.email]]);
  if (user.avatarUrl) crisp.push(['set', 'user:avatar', [user.avatarUrl]]);

  /**@type {[string, string][]} */
  const data = [];
  data.push(['UUID', user.id]);
  data.push(['Plan', plan?.name || 'N/A']);
  data.push(['ExpireTime', planExpiry ? formatDate(planExpiry) : 'N/A']);
  data.push(['Traffic', formatFilesize(trafficTotal)]);
  data.push(['TrafficUsed', formatFilesize(trafficUsed)]);
  data.push(['Balance', user.balance.toString()]);
  data.push(['Commission', user.commission.toString()]);
  data.push(['CommissionPending', user.commissionInProgress.toString()]);
  data.push(['CommissionTotal', user.commissionTotal.toString()]);
  data.push(['RegistrationTime', formatDate(user.createdAt)]);

  crisp.push(['set', 'session:data', [data]]);
}

</script>