本文以小程序为例,介绍如何通过重载的方法让 authing-js-sdk 适配不同端环境。由于小程序端环境载网络请求、本地缓存、密码加密运行环境上都存在差异,所以需要针对这些差异性重载相关类。
如果你想适配其他环境,比如 uni app,也可以参照此指引来适配该环境。
uni app 可以使用 uni-axios 来做网络请求兼容。
由于小程序环境下不能使用 axios
,所以需要重载 authing-js-sdk
的网络请求类:
- HttpClient
- GraphqlClient
原始的 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 类定义如下:
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;
}
}
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);
}
}
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