1 |
|
2 |
|
3 |
|
4 | import { ConsoleLogger as Logger } from './Logger';
|
5 |
|
6 | const logger = new Logger('Hub');
|
7 |
|
8 | const AMPLIFY_SYMBOL = (
|
9 | typeof Symbol !== 'undefined' && typeof Symbol.for === 'function'
|
10 | ? Symbol.for('amplify_default')
|
11 | : '@@amplify_default'
|
12 | ) as Symbol;
|
13 | interface IPattern {
|
14 | pattern: RegExp;
|
15 | callback: HubCallback;
|
16 | }
|
17 |
|
18 | interface IListener {
|
19 | name: string;
|
20 | callback: HubCallback;
|
21 | }
|
22 |
|
23 | export type HubCapsule = {
|
24 | channel: string;
|
25 | payload: HubPayload;
|
26 | source: string;
|
27 | patternInfo?: string[];
|
28 | };
|
29 |
|
30 | export type HubPayload = {
|
31 | event: string;
|
32 | data?: any;
|
33 | message?: string;
|
34 | };
|
35 |
|
36 | export type HubCallback = (capsule: HubCapsule) => void;
|
37 |
|
38 | export type LegacyCallback = { onHubCapsule: HubCallback };
|
39 |
|
40 | function isLegacyCallback(callback: any): callback is LegacyCallback {
|
41 | return (<LegacyCallback>callback).onHubCapsule !== undefined;
|
42 | }
|
43 |
|
44 | export class HubClass {
|
45 | name: string;
|
46 | private listeners: IListener[] = [];
|
47 | private patterns: IPattern[] = [];
|
48 |
|
49 | protectedChannels = [
|
50 | 'core',
|
51 | 'auth',
|
52 | 'api',
|
53 | 'analytics',
|
54 | 'interactions',
|
55 | 'pubsub',
|
56 | 'storage',
|
57 | 'ui',
|
58 | 'xr',
|
59 | ];
|
60 |
|
61 | constructor(name: string) {
|
62 | this.name = name;
|
63 | }
|
64 |
|
65 | |
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | private _remove(channel: string | RegExp, listener: HubCallback) {
|
72 | if (channel instanceof RegExp) {
|
73 | const pattern = this.patterns.find(
|
74 | ({ pattern }) => pattern.source === channel.source
|
75 | );
|
76 | if (!pattern) {
|
77 | logger.warn(`No listeners for ${channel}`);
|
78 | return;
|
79 | }
|
80 | this.patterns = [...this.patterns.filter(x => x !== pattern)];
|
81 | } else {
|
82 | const holder = this.listeners[channel];
|
83 | if (!holder) {
|
84 | logger.warn(`No listeners for ${channel}`);
|
85 | return;
|
86 | }
|
87 | this.listeners[channel] = [
|
88 | ...holder.filter(({ callback }) => callback !== listener),
|
89 | ];
|
90 | }
|
91 | }
|
92 |
|
93 | |
94 |
|
95 |
|
96 | remove(channel: string | RegExp, listener: HubCallback) {
|
97 | this._remove(channel, listener);
|
98 | }
|
99 |
|
100 | |
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | dispatch(
|
110 | channel: string,
|
111 | payload: HubPayload,
|
112 | source: string = '',
|
113 | ampSymbol?: Symbol
|
114 | ) {
|
115 | if (this.protectedChannels.indexOf(channel) > -1) {
|
116 | const hasAccess = ampSymbol === AMPLIFY_SYMBOL;
|
117 |
|
118 | if (!hasAccess) {
|
119 | logger.warn(
|
120 | `WARNING: ${channel} is protected and dispatching on it can have unintended consequences`
|
121 | );
|
122 | }
|
123 | }
|
124 |
|
125 | const capsule: HubCapsule = {
|
126 | channel,
|
127 | payload: { ...payload },
|
128 | source,
|
129 | patternInfo: [],
|
130 | };
|
131 |
|
132 | try {
|
133 | this._toListeners(capsule);
|
134 | } catch (e) {
|
135 | logger.error(e);
|
136 | }
|
137 | }
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 | listen(
|
149 | channel: string | RegExp,
|
150 | callback?: HubCallback | LegacyCallback,
|
151 | listenerName = 'noname'
|
152 | ) {
|
153 | let cb: HubCallback;
|
154 |
|
155 | if (isLegacyCallback(callback)) {
|
156 | logger.warn(
|
157 | `WARNING onHubCapsule is Deprecated. Please pass in a callback.`
|
158 | );
|
159 | cb = callback.onHubCapsule.bind(callback);
|
160 | } else if (typeof callback !== 'function') {
|
161 | throw new Error('No callback supplied to Hub');
|
162 | } else {
|
163 | cb = callback;
|
164 | }
|
165 |
|
166 | if (channel instanceof RegExp) {
|
167 | this.patterns.push({
|
168 | pattern: channel,
|
169 | callback: cb,
|
170 | });
|
171 | } else {
|
172 | let holder = this.listeners[channel];
|
173 |
|
174 | if (!holder) {
|
175 | holder = [];
|
176 | this.listeners[channel] = holder;
|
177 | }
|
178 |
|
179 | holder.push({
|
180 | name: listenerName,
|
181 | callback: cb,
|
182 | });
|
183 | }
|
184 |
|
185 | return () => {
|
186 | this._remove(channel, cb);
|
187 | };
|
188 | }
|
189 |
|
190 | private _toListeners(capsule: HubCapsule) {
|
191 | const { channel, payload } = capsule;
|
192 | const holder = this.listeners[channel];
|
193 |
|
194 | if (holder) {
|
195 | holder.forEach(listener => {
|
196 | logger.debug(`Dispatching to ${channel} with `, payload);
|
197 | try {
|
198 | listener.callback(capsule);
|
199 | } catch (e) {
|
200 | logger.error(e);
|
201 | }
|
202 | });
|
203 | }
|
204 |
|
205 | if (this.patterns.length > 0) {
|
206 | if (!payload.message) {
|
207 | logger.warn(`Cannot perform pattern matching without a message key`);
|
208 | return;
|
209 | }
|
210 |
|
211 | const payloadStr = payload.message;
|
212 |
|
213 | this.patterns.forEach(pattern => {
|
214 | const match = payloadStr.match(pattern.pattern);
|
215 | if (match) {
|
216 | const [, ...groups] = match;
|
217 | const dispatchingCapsule: HubCapsule = {
|
218 | ...capsule,
|
219 | patternInfo: groups,
|
220 | };
|
221 | try {
|
222 | pattern.callback(dispatchingCapsule);
|
223 | } catch (e) {
|
224 | logger.error(e);
|
225 | }
|
226 | }
|
227 | });
|
228 | }
|
229 | }
|
230 | }
|
231 |
|
232 |
|
233 |
|
234 |
|
235 | export const Hub = new HubClass('__default__');
|