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