Skip to content
This repository has been archived by the owner on Feb 26, 2023. It is now read-only.

Latest commit

 

History

History
506 lines (425 loc) · 14.2 KB

适配不同端环境指引.md

File metadata and controls

506 lines (425 loc) · 14.2 KB

authing-js-sdk 适配不同端环境指引

本文以小程序为例,介绍如何通过重载的方法让 authing-js-sdk 适配不同端环境。由于小程序端环境载网络请求、本地缓存、密码加密运行环境上都存在差异,所以需要针对这些差异性重载相关类。

如果你想适配其他环境,比如 uni app,也可以参照此指引来适配该环境。

uni app 可以使用 uni-axios 来做网络请求兼容。

网络请求

由于小程序环境下不能使用 axios,所以需要重载 authing-js-sdk 的网络请求类:

  • HttpClient
  • GraphqlClient

HttpClient

原始的 HttpClient 类定义如下:

import { SDK_VERSION } from '../version';
import { ManagementClientOptions } from '../management/types';
import { AuthenticationClientOptions } from '../authentication/types';
import { AuthenticationTokenProvider } from '../authentication/AuthenticationTokenProvider';
import { ManagementTokenProvider } from '../management/ManagementTokenProvider';
import Axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

export class HttpClient {
  options: ManagementClientOptions;
  tokenProvider: ManagementTokenProvider | AuthenticationTokenProvider;
  axios: AxiosInstance;

  constructor(
    options: ManagementClientOptions | AuthenticationClientOptions,
    tokenProvider: ManagementTokenProvider | AuthenticationTokenProvider
  ) {
    this.options = options;
    this.tokenProvider = tokenProvider;
    this.axios = Axios.create({
      withCredentials: true
    });
  }

  async request(config: AxiosRequestConfig) {
    const headers: any = {
      'x-authing-sdk-version': `js:${SDK_VERSION}`,
      'x-authing-userpool-id': this.options.userPoolId || '',
      'x-authing-request-from': this.options.requestFrom || 'sdk',
      'x-authing-app-id': this.options.appId || ''
    };
    if (!(config && config.headers && config.headers.authorization)) {
      // 如果用户不传 token,就使用 sdk 自己维护的
      const token = await this.tokenProvider.getToken();
      token && (headers.Authorization = `Bearer ${token}`);
    } else {
      headers.authorization = config.headers.authorization;
    }
    config.headers = headers;
    config.timeout = this.options.timeout;
    const { data } = await this.axios.request(config);
    const { code, message } = data;
    if (code !== 200) {
      this.options.onError(code, message, data.data);
      throw new Error(JSON.stringify({ code, message, data: data.data }));
    }
    return data.data;
  }
}

为了兼容小程序环境,将 axios 替换为 wx-axios

import {
  AuthenticationClientOptions,
  AuthenticationTokenProvider,
  ManagementClientOptions,
  ManagementTokenProvider,
} from "authing-js-sdk";
import { AxiosInstance, AxiosRequestConfig } from "axios";
import { VERSION } from "./version";
import Axios from "wx-axios";

export class HttpClient {
  options: ManagementClientOptions;
  tokenProvider: ManagementTokenProvider | AuthenticationTokenProvider;
  axios: AxiosInstance;

  constructor(
    options: ManagementClientOptions | AuthenticationClientOptions,
    tokenProvider: ManagementTokenProvider | AuthenticationTokenProvider
  ) {
    this.options = options;
    this.tokenProvider = tokenProvider;
    // @ts-ignore
    this.axios = Axios.create();
  }

  async request(config: AxiosRequestConfig) {
    const headers: any = {
      "x-authing-sdk-version": `wxapp:${VERSION}`,
      "x-authing-userpool-id": this.options.userPoolId,
      "x-authing-request-from": "wxapp",
      "x-authing-app-id": this.options.appId || "",
    };
    if (!(config && config.headers && config.headers.authorization)) {
      // 如果用户不传 token,就使用 sdk 自己维护的
      const token = await this.tokenProvider.getToken();
      token && (headers.Authorization = `Bearer ${token}`);
    } else {
      headers.authorization = config.headers.authorization;
    }
    config.headers = headers;
    config.timeout = this.options.timeout;
    const { data } = await this.axios.request(config);
    const { code, message } = data;
    if (code !== 200) {
      this.options.onError && this.options.onError(code, message, data.data);
      throw new Error(JSON.stringify({ code, message, data: data.data }));
    }
    return data.data;
  }
}

GraphqlClient

原始的 GraphqlClient 类定义如下:

import { SDK_VERSION } from '../version';
import { ManagementClientOptions } from '../management/types';
import { AuthenticationClientOptions } from '../authentication/types';
import Axios, { AxiosInstance } from 'axios';

export class GraphqlClient {
  endpoint: string;
  options: ManagementClientOptions | AuthenticationClientOptions;
  axios: AxiosInstance;

  constructor(
    endpoint: string,
    options: ManagementClientOptions | AuthenticationClientOptions
  ) {
    this.endpoint = endpoint;
    this.options = options;
    this.axios = Axios.create({
      withCredentials: true
    });
  }

  async request(options: { query: string; variables?: any; token?: string }) {
    const { query, token, variables } = options;
    let headers: any = {
      'content-type': 'application/json',
      'x-authing-sdk-version': `js:${SDK_VERSION}`,
      'x-authing-userpool-id': this.options.userPoolId || '',
      'x-authing-request-from': this.options.requestFrom || 'sdk',
      'x-authing-app-id': this.options.appId || ''
    };
    token && (headers.Authorization = `Bearer ${token}`);
    let data = null;
    let errors = null;
    try {
      let { data: responseData } = await this.axios({
        url: this.endpoint,
        data: {
          query,
          variables
        },
        method: 'post',
        headers,
        timeout: this.options.timeout
      });
      data = responseData.data;
      errors = responseData.errors;
    } catch (error) {
      console.log(error);
      this.options.onError(500, '网络请求错误', null);
      throw { code: 500, message: '网络请求错误', data: null };
    }

    if (errors?.length > 0) {
      let errmsg = null;
      let errcode = null;
      let data = null;
      errors.map((err: any) => {
        const { message: msg } = err;
        const { code, message, data: _data } = msg;
        errcode = code;
        errmsg = message;
        data = _data;
        this.options.onError(code, message, data);
      });
      throw { code: errcode, message: errmsg, data };
    }

    return data;
  }
}

为了兼容小程序环境,将 axios 替换为 wx-axios

import {
  AuthenticationClientOptions,
  ManagementClientOptions,
} from "authing-js-sdk";
import Axios from "wx-axios";
import { VERSION } from "./version";

export class GraphqlClient {
  endpoint: string;
  options: AuthenticationClientOptions | AuthenticationClientOptions;
  axios: any;

  constructor(
    endpoint: string,
    options: ManagementClientOptions | AuthenticationClientOptions
  ) {
    this.endpoint = endpoint;
    this.options = options;
    this.axios = Axios.create();
  }

  async request(options: { query: string; variables?: any; token?: string }) {
    const { query, token, variables } = options;
    let headers: any = {
      "content-type": "application/json",
      "x-authing-sdk-version": `wxapp:${VERSION}`,
      "x-authing-userpool-id": this.options.userPoolId,
      "x-authing-request-from": "wxapp",
      "x-authing-app-id": this.options.appId || "",
    };
    token && (headers.Authorization = `Bearer ${token}`);
    let data = null;
    let errors = null;
    try {
      let { data: responseData } = await this.axios({
        url: this.endpoint,
        data: {
          query,
          variables,
        },
        method: "post",
        headers,
        timeout: this.options.timeout,
      });
      data = responseData.data;
      errors = responseData.errors;
    } catch (error) {
      this.options.onError && this.options.onError(500, "网络请求错误", null);
      throw { code: 500, message: "网络请求错误", data: null };
    }

    if (errors?.length > 0) {
      let errmsg = null;
      let errcode = null;
      let data = null;
      errors.map((err: any) => {
        const { message: msg } = err;
        const { code, message, data: _data } = msg;
        errcode = code;
        errmsg = message;
        data = _data;
        this.options.onError && this.options.onError(code, message, data);
      });
      throw { code: errcode, message: errmsg, data };
    }

    return data;
  }
}

重载 authing-js-sdk 的 AuthenticationClient

import {
  AuthenticationClient as BaseAuthenticationClient,
  AuthenticationClientOptions,
} from "authing-js-sdk";
import { GraphqlClient } from "./GraphqlClient";
import { HttpClient } from "./HttpClient";

export class AuthenticationClient extends BaseAuthenticationClient {
  constructor(options: AuthenticationClientOptions) {
    options.httpClient = HttpClient;
    options.graphqlClient = GraphqlClient;
    super(options);
  }
}

重载 authing-js-sdk 的 ManagementClient

import {
  ManagementClient as BaseManagementClient,
  ManagementClientOptions,
} from "authing-js-sdk";
import { GraphqlClient } from "./GraphqlClient";
import { HttpClient } from "./HttpClient";

export class ManagementClient extends BaseManagementClient {
  constructor(options: ManagementClientOptions) {
    options.httpClient = HttpClient;
    options.graphqlClient = GraphqlClient;
    super(options);
  }
}

本地缓存

当用户完成登陆之后,Authing 会在端环境的本地缓存中存储用户信息和 token,以记住用户的登录状态。由于小程序中无法引用 localStorage,所以需要使用 wx.storage 相关方法进行重载 AuthenticationTokenProvider

原始的 AuthenticationTokenProvider

import { User } from '../../types/graphql.v2';
import { AuthenticationClientOptions } from './types';

const tokenKey = '_authing_token';
const userKey = '_authing_user';

export class AuthenticationTokenProvider {
  options: AuthenticationClientOptions;

  private token?: string;
  private user?: User;

  constructor(options: AuthenticationClientOptions) {
    this.options = options;

    // 为了兼容服务端不支持 localStorage 的情况
    this.token = null;
    this.user = null;
  }

  setToken(token: string) {
    if (typeof localStorage !== 'undefined') {
      localStorage.setItem(tokenKey, token);
    } else {
      this.token = token;
    }
  }

  getToken() {
    return typeof localStorage !== 'undefined'
      ? localStorage.getItem(tokenKey) || ''
      : this.token;
  }

  getUser(): User | null {
    return typeof localStorage !== 'undefined'
      ? localStorage.getItem(userKey)
        ? JSON.parse(localStorage.getItem(userKey))
        : null
      : this.user;
  }

  setUser(user: User) {
    if (typeof localStorage !== 'undefined') {
      localStorage.setItem(userKey, JSON.stringify(user));
      localStorage.setItem(tokenKey, user.token);
    } else {
      this.user = user;
      this.token = user.token;
    }
  }

  clearUser() {
    if (typeof localStorage !== 'undefined') {
      localStorage.removeItem(userKey);
      localStorage.removeItem(tokenKey);
    } else {
      this.user = null;
      this.token = null;
    }
  }
}

使用 wx.storage 相关方法替换 localStorage 之后:

import { AuthenticationClientOptions, User } from "authing-js-sdk";

const tokenKey = "_authing_token";
const userKey = "_authing_user";

export class MiniprogramTokenProvider {
  options: AuthenticationClientOptions;

  constructor(options: AuthenticationClientOptions) {
    this.options = options;
  }

  setToken(token: string) {
    wx.setStorageSync(tokenKey, token);
  }

  getToken() {
    return wx.getStorageSync(tokenKey);
  }

  getUser(): User | null {
    return wx.getStorageSync(userKey);
  }

  setUser(user: User) {
    wx.setStorageSync(userKey, user);
    wx.setStorageSync(tokenKey, user.token as string);
  }

  clearUser() {
    wx.removeStorageSync(userKey);
    wx.removeStorageSync(tokenKey);
  }
}

接着重载 AuthenticationClient:

import {
  AuthenticationClient as BaseAuthenticationClient,
  AuthenticationClientOptions,
} from "authing-js-sdk";
import { MiniprogramTokenProvider } from "./TokenProvider";

export class AuthenticationClient extends BaseAuthenticationClient {
  constructor(options: AuthenticationClientOptions) {
    options.tokenProvider = MiniprogramTokenProvider;
    super(options);
  }
}

密码加密

由于浏览器环境下的 crypto-js 在小程序环境中无法使用,所以引入了 src/wxapp-jsencrpt.js 作为自定义密码加密函数。

const { JSEncrypt } = require("./wxapp-jsencrpt.js");

export const encryptFunction = async (plainText: string, publicKey: string) => {
  const encrypt = new JSEncrypt();
  encrypt.setPublicKey(publicKey);
  const encStr = encrypt.encrypt(plainText);
  return encStr.toString();
};

接着重载 AuthenticationClient:

import {
  AuthenticationClient as BaseAuthenticationClient,
  AuthenticationClientOptions,
} from "authing-js-sdk";
import { encryptFunction } from "./encrypt";

export class AuthenticationClient extends BaseAuthenticationClient {
  constructor(options: AuthenticationClientOptions) {
    options.encryptFunction = encryptFunction;
    super(options);
  }
}

调用重载过后的类

重载过后的类调用方式和 authing-js-sdk 的方法完全一致,

以重载过后的 AuthenticationClient 为例:

重载的类:

import {
  AuthenticationClient as BaseAuthenticationClient,
  AuthenticationClientOptions,
} from "authing-js-sdk";

export class AuthenticationClient extends BaseAuthenticationClient {
  constructor(options: AuthenticationClientOptions) {
    // extends here
    super(options);
  }
}

调用示例:

const authing = new AuthenticationClient({
  appId: "YOUR_APP_ID",
})

const email = '[email protected]';
const password = 'passw0rd';
const user = await authing.loginByEmail(email, password); // 成功登录,将 token 写入 localStorage