1 | /* eslint-disable max-len */
|
2 |
|
3 | /**
|
4 | * 插件接口
|
5 | *
|
6 | * <p>
|
7 | * 插件是由一个或多个扩展点组成的字典。SDK 的扩展点可以分为两类:
|
8 | * <p>
|
9 | * 第一类扩展点是类实例化之后的回调,包括 <code>Realtime</code>、<code>IMClient</code> 与 <code>Conversation</code>。这些扩展点可以通过一个同步的 Decorator 进行扩展。Decorator 接受一个对应的实例并对其进行一些操作。
|
10 | * 特别的,由于注册自定义消息类这个需求特别的常用,额外定义一个 messageClasses 扩展点来做这件事情。
|
11 | * <p>
|
12 | * 第二类扩展点是在某些事件处理前、后可以注入逻辑的点。
|
13 | * 其中 <code>beforeMessageParse</code>,<code>afterMessageParse</code> 可以通过一个异步的 Middleware 进行扩展。Middleware 接受一个对象,返回一个同类型对象或同类型对象的 Promise。
|
14 | * <code>beforeMessageDispatch</code> 可以通过返回一个 boolean 类型的 shouldDispatch 值来控制是否要继续派发收到的消息。
|
15 | * <p>
|
16 | * 如果使用了多个插件,这些 hook 会按照插件数组的顺序依次执行。前一个 Middleware 的返回值会作为参数传给后一个 Middleware。
|
17 | *
|
18 | * @interface Plugin
|
19 | * @since 3.1.0
|
20 | */
|
21 |
|
22 | /* eslint-enable max-len */
|
23 |
|
24 | /**
|
25 | * 插件名称,用于在日志中显示异常的插件
|
26 | *
|
27 | * @name Plugin.name
|
28 | * @type string
|
29 | */
|
30 |
|
31 | /**
|
32 | * 插件注册的消息类型
|
33 | *
|
34 | * @name Plugin.messageClasses
|
35 | * @type AVMessage[]
|
36 | */
|
37 |
|
38 | /**
|
39 | * 在 Realtime 实例化后对其进行修饰。
|
40 | * <p>
|
41 | * 接受一个参数为 Realtime 实例。
|
42 | *
|
43 | * @name Plugin.onRealtimeCreate
|
44 | * @type Function
|
45 | */
|
46 |
|
47 | /**
|
48 | * 在 IMClient 实例化后对其进行修饰。
|
49 | * <p>
|
50 | * 接受一个参数为 IMClient 实例。
|
51 | *
|
52 | * @name Plugin.onIMClientCreate
|
53 | * @type Function
|
54 | */
|
55 |
|
56 | /**
|
57 | * 在 Conversation 实例化后对其进行修饰。
|
58 | * <p>
|
59 | * 接受一个参数为 Conversation 实例。
|
60 | * 需要注意的是,该扩展点并不是在 <code>{@link IMClient#createConversation}</code> 方法创建成功后调用的 hook,
|
61 | * 而是所有的 Conversation 实例化的时候(包括但不限于 query 时)调用的 hook。
|
62 | *
|
63 | * @name Plugin.onConversationCreate
|
64 | * @type Function
|
65 | */
|
66 |
|
67 | /**
|
68 | * 在对消息进行 parse 之前,对原始消息进行修改。
|
69 | * <p>
|
70 | * 接受一个参数为原始消息,是某个消息的内容,一般是一个 JSON 对象。
|
71 | * 该方法需要返回一个 JSON 对象。如果这个结果是异步得到的,也可以返回一个 Promise(fulfilled with a JSON)。
|
72 | *
|
73 | * @name Plugin.beforeMessageParse
|
74 | * @type Function
|
75 | */
|
76 |
|
77 | /**
|
78 | * 在对消息进行 parse 之后,对消息实例进行修改。
|
79 | * <p>
|
80 | * 接受一个参数为消息实例,一般是一个已注册的 Message 类或其子类的实例。
|
81 | * 该方法需要返回一个同类型的消息实例。如果这个结果是异步得到的,也可以返回一个 Promise。
|
82 | *
|
83 | * @name Plugin.afterMessageParse
|
84 | * @type Function
|
85 | */
|
86 |
|
87 | /**
|
88 | * 在收到消息之后,派发消息之前,控制是否派发这条消息。
|
89 | * <p>
|
90 | * 接受参数为 message 与 conversation。
|
91 | * 该方法需要返回 boolean 类型的值,如果返回 false 则 SDK 不再派发这条消息,后续的 beforeMessageDispatch 也不会执行。
|
92 | * 如果这个结果是异步得到的,也可以返回一个 Promise。
|
93 | *
|
94 | * @name Plugin.beforeMessageDispatch
|
95 | * @type Function
|
96 | * @since 3.4.0
|
97 | */
|
98 |
|
99 | import { ensureArray, tap } from './utils';
|
100 |
|
101 | const checkType = middleware => param => {
|
102 | const { constructor } = param;
|
103 | return Promise.resolve(param)
|
104 | .then(middleware)
|
105 | .then(
|
106 | tap(result => {
|
107 | if (result === undefined || result === null) {
|
108 | // eslint-disable-next-line max-len
|
109 | return console.warn(
|
110 | `Middleware[${middleware._pluginName ||
|
111 | 'anonymous plugin'}:${middleware.name ||
|
112 | 'anonymous middleware'}] param/return types not match. It returns ${result} while a ${
|
113 | param.constructor.name
|
114 | } expected.`
|
115 | );
|
116 | }
|
117 | if (!(result instanceof constructor)) {
|
118 | // eslint-disable-next-line max-len
|
119 | return console.warn(
|
120 | `Middleware[${middleware._pluginName ||
|
121 | 'anonymous plugin'}:${middleware.name ||
|
122 | 'anonymous middleware'}] param/return types not match. It returns a ${
|
123 | result.constructor.name
|
124 | } while a ${param.constructor.name} expected.`
|
125 | );
|
126 | }
|
127 | return 0;
|
128 | })
|
129 | );
|
130 | };
|
131 |
|
132 | export const applyDecorators = (decorators, target) => {
|
133 | if (decorators) {
|
134 | decorators.forEach(decorator => {
|
135 | try {
|
136 | decorator(target);
|
137 | } catch (error) {
|
138 | if (decorator._pluginName) {
|
139 | error.message += `[${decorator._pluginName}]`;
|
140 | }
|
141 | throw error;
|
142 | }
|
143 | });
|
144 | }
|
145 | };
|
146 |
|
147 | export const applyMiddlewares = middlewares => target =>
|
148 | ensureArray(middlewares).reduce(
|
149 | (previousPromise, middleware) =>
|
150 | previousPromise.then(checkType(middleware)).catch(error => {
|
151 | if (middleware._pluginName) {
|
152 | // eslint-disable-next-line no-param-reassign
|
153 | error.message += `[${middleware._pluginName}]`;
|
154 | }
|
155 | throw error;
|
156 | }),
|
157 | Promise.resolve(target)
|
158 | );
|
159 |
|
160 | export const applyDispatcher = (dispatchers, payload) =>
|
161 | ensureArray(dispatchers).reduce(
|
162 | (resultPromise, dispatcher) =>
|
163 | resultPromise
|
164 | .then(
|
165 | shouldDispatch =>
|
166 | shouldDispatch === false ? false : dispatcher(...payload)
|
167 | )
|
168 | .catch(error => {
|
169 | if (dispatcher._pluginName) {
|
170 | // eslint-disable-next-line no-param-reassign
|
171 | error.message += `[${dispatcher._pluginName}]`;
|
172 | }
|
173 | throw error;
|
174 | }),
|
175 | Promise.resolve(true)
|
176 | );
|