UNPKG

5.03 kBPlain TextView Raw
1/*
2 * Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5 * the License. A copy of the License is located at
6 *
7 * http://aws.amazon.com/apache2.0/
8 *
9 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11 * and limitations under the License.
12 */
13
14import { ConsoleLogger as Logger } from './Logger';
15
16const logger = new Logger('Hub');
17
18const AMPLIFY_SYMBOL = (typeof Symbol !== 'undefined' &&
19typeof Symbol.for === 'function'
20 ? Symbol.for('amplify_default')
21 : '@@amplify_default') as Symbol;
22interface IPattern {
23 pattern: RegExp;
24 callback: HubCallback;
25}
26
27interface IListener {
28 name: string;
29 callback: HubCallback;
30}
31
32export type HubCapsule = {
33 channel: string;
34 payload: HubPayload;
35 source: string;
36 patternInfo?: string[];
37};
38
39export type HubPayload = {
40 event: string;
41 data?: any;
42 message?: string;
43};
44
45export type HubCallback = (capsule: HubCapsule) => void;
46
47export type LegacyCallback = { onHubCapsule: HubCallback };
48
49function isLegacyCallback(callback: any): callback is LegacyCallback {
50 return (<LegacyCallback>callback).onHubCapsule !== undefined;
51}
52
53export 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 // Note - Need to pass channel as a reference for removal to work and not anonymous function
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 // Check for legacy onHubCapsule callback for backwards compatability
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/*We export a __default__ instance of HubClass to use it as a
212pseudo Singleton for the main messaging bus, however you can still create
213your own instance of HubClass() for a separate "private bus" of events.*/
214export const Hub = new HubClass('__default__');
215/**
216 * @deprecated use named import
217 */
218export default Hub;