# 跨端开发

tcb-js-sdk 只支持常规 Web 应用(即浏览器环境)的开发,不兼容其他类 Web 平台,比如微信小程序、快应用、Cocos 等。虽然这些平台大多支持 JavaScript 运行环境,但在网络请求、本地存储、平台标识等特性上与浏览器环境有明显差异。针对这些差异特性,tcb-js-sdk 提供一套完整的适配扩展方案,遵循此方案规范可开发对应平台的适配器,然后搭配 tcb-js-sdk 和适配器实现平台的兼容性。

WARNING

新版 JavaScript SDK 已更名 @cloudbase/js-sdk ,旧版本 tcb-js-sdk 未来不再更新,点击查看迁移指南

# 适配规范

开发适配器之前需要安装官方提供的接口声明模块@cloudbase/adapter-interface (opens new window)

# npm
npm i @cloudbase/adapter-interface
# yarn
yarn add @cloudbase/adapter-interface

适配器模块需要导出一个adapter对象:

const adapter = {
  genAdapter,
  isMatch,
  // runtime标记平台唯一性
  runtime: '平台名称'
};

export adapter;
export default adapter;

必须包含以下三个字段:

  • runtime: string,平台的名称,用于标记平台唯一性;
  • isMatch: Function,判断当前运行环境是否为平台,返回boolean值;
  • genAdapter: Function,创建adapter实体。

# runtime

runtime用于标记平台的唯一性,建议尽量以平台的英文名称或简写命名,比如百度小程序baidu_miniapp、QQ 小程序qq_miniapp等等。

# isMatch

isMatch函数用于判断当前运行环境是否与适配器匹配,通常是通过判断平台特有的一些全局变量、API 等。比如以下代码是判断运行环境是否为 Cocos 原生平台:

function isMatch(): boolean {
  if (typeof cc === "undefined") {
    return false;
  }
  if (typeof WebSocket === "undefined") {
    return false;
  }
  if (typeof XMLHttpRequest === "undefined") {
    return false;
  }

  if (!cc.game) {
    return false;
  }
  if (typeof cc.game.on !== "function") {
    return false;
  }
  if (!cc.game.EVENT_HIDE) {
    return false;
  }
  if (!cc.game.EVENT_SHOW) {
    return false;
  }

  if (!cc.sys) {
    return false;
  }
  if (!cc.sys.isNative) {
    return false;
  }

  return true;
}

# genAdapter

genAdapter函数返回适配器的实体对象,结构如下:

interface SDKAdapterInterface {
  // 全局根变量,浏览器环境为window
  root: any;
  // WebSocket类
  wsClass: WebSocketContructor;
  // request类
  reqClass: SDKRequestConstructor;
  // 无localstorage时persistence=local降级为none
  localStorage?: StorageInterface;
  // 无sessionStorage时persistence=session降级为none
  sessionStorage?: StorageInterface;
  // storage模式首选,优先级高于persistence
  primaryStorage?: StorageType;
  // 获取平台唯一应用标识的api
  getAppSign?(): string;
}

# 示例

import {
  AbstractSDKRequest,
  IRequestOptions,
  IUploadRequestOptions,
  StorageInterface,
  WebSocketInterface,
  WebSocketContructor,
  SDKAdapterInterface,
  StorageType,
  formatUrl
} from "@cloudbase/adapter-interface";

// isMatch函数判断当前平台是否匹配
function isMatch(): boolean {
  // ...
  return true;
}

// Request类为平台特有的网络请求,必须实现post/upload/download三个public接口
export class Request extends AbstractSDKRequest {
  // 实现post接口
  public post(options: IRequestOptions) {
    return new Promise((resolve) => {
      // ...
      resolve();
    });
  }
  // 实现upload接口
  public upload(options: IUploadRequestOptions) {
    return new Promise((resolve) => {
      // ...
      resolve();
    });
  }
  // 实现download接口
  public download(options: IRequestOptions) {
    return new Promise((resolve) => {
      // ...
      resolve();
    });
  }
}
// Storage为平台特有的本地存储,必须实现setItem/getItem/removeItem/clear四个接口
export const Storage: StorageInterface = {
  setItem(key: string, value: any) {
    // ...
  },
  getItem(key: string): any {
    // ...
  },
  removeItem(key: string) {
    // ...
  },
  clear() {
    // ...
  }
};
// WebSocket为平台特有的WebSocket,与HTML5标准规范一致
export class WebSocket {
  constructor(url: string, options: object = {}) {
    const socketTask: WebSocketInterface = {
      set onopen(cb) {
        // ...
      },
      set onmessage(cb) {
        // ...
      },
      set onclose(cb) {
        // ...
      },
      set onerror(cb) {
        // ...
      },
      send: (data) => {
        // ...
      },
      close: (code?: number, reason?: string) => {
        // ...
      },
      get readyState() {
        // ...
        return readyState;
      },
      CONNECTING: 0,
      OPEN: 1,
      CLOSING: 2,
      CLOSED: 3
    };
    return socketTask;
  }
}

// genAdapter函数创建adapter实体
function genAdapter() {
  const adapter: SDKAdapterInterface = {
    // root对象为全局根对象,没有则填空对象{}
    root: window,
    reqClass: Request,
    wsClass: WebSocket as WebSocketContructor,
    localStorage: Storage,
    // 首先缓存存放策略,建议始终保持localstorage
    primaryStorage: StorageType.local,
    // sessionStorage为可选项,如果平台不支持可不填
    sessionStorage: sessionStorage
  };
  return adapter;
}

// 三者缺一不可
const adapter = {
  genAdapter,
  isMatch,
  // runtime标记平台唯一性
  runtime: "平台名称"
};

export default adapter;

# 接入流程

# 第 1 步:安装并引入适配器

安装 tcb-js-sdk 和所需平台的适配器,比如 QQ 小游戏平台:

# 安装 tcb-js-sdk
npm i tcb-js-sdk
# 安装 QQ 小游戏适配器
npm i cloudbase-adapter-qq_game

然后在业务代码中将引入适配器:

import tcb from "tcb-js-sdk";
import adapter from "cloudbase-adapter-qq_game";

tcb.useAdapters(adapter);

# 第 2 步:配置安全应用来源

云开发需验证请求来源的合法性,常规 Web 通过验证安全域名,而由于上文提到的诸多类 Web 环境并没有域名的概念,所以需要借助安全应用凭证区分请求来源是否合法。

登录云开发 CloudBase 控制台 (opens new window),在安全配置 (opens new window)页面中的移动应用安全来源一栏:

点击“添加应用”按钮,输入应用标识:

WARNING

应用标识必须是能够标记应用唯一性的信息,比如微信小程序的appId、移动应用的包名等。

添加成功后会创建一个安全应用的信息,如下图所示:

# 第 3 步:初始化云开发

在业务代码中初始化云开发时将第 2 步配置的安全应用信息作为参数传递给 init 方法:

import tcb from 'tcb-js-sdk';
import adapter from 'cloudbase-adapter-qq_game';

tcb.useAdapters(adapter);

tcb.init({
  env: '环境ID',
  appSign: '应用标识',
  appSecret: {
    appAccessKeyId: '应用凭证版本号'
    appAccessKey: '应用凭证'
  }
})
  • appSignstring,应用标识,对应上图中“应用标识”一栏;
  • appSecretObject,应用凭证信息,包括以下字段:
    • appAccessKeyIdstring,对应上图中“版本”一栏,同一个应用标识可以最多可以添加两个版本的凭证信息,以便区分开发和生产环境;
    • appAccessKeystring,对应上图中“操作”一栏点击“获取凭证”之后获取到的信息。

TIP

appAccessKeyappAccessKeyId必须一一对应。

# 第 4 步:编写业务代码

经过以上准备工作之后便可以编写自身的业务代码。

# 已有适配器

目前已有的适配器可以在云开发社区官网 (opens new window)中查看。

TIP

所有适配器均来自社区贡献,欢迎更多开发者加入。

# 一套代码多端适配

如果您需要将一套代码兼容多种平台,tcb-js-sdk 可以同时引入多个适配器,在运行时通过各适配器的isMatch函数判断平台类型,然后引入对应的兼容逻辑。比如以下代码可以同时兼容 QQ 小游戏、 Cocos 原生和百度小游戏三种平台:

import tcb from 'tcb-js-sdk';
import adapter as adapterOfQQGame from 'cloudbase-adapter-qq_game';
import adapter as adapterOfCocosNative from 'cloudbase-adapter-cocos_native';
import adapter as adapterOfBDGame from 'cloudbase-adapter-bd_game';

tcb.useAdapters([
  adapterOfQQGame,
  adapterOfCocosNative,
  adapterOfBDGame
]);