@wildix/xbees-connect 1.2.0-alpha.3 → 1.2.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-es/package.json +9 -2
- package/dist-es/src/Client.js +7 -2
- package/dist-es/src/__tests__/Client.test.js +259 -0
- package/dist-es/src/__tests__/MessageListener.test.js +100 -0
- package/dist-es/src/helpers/ClientParams.js +3 -0
- package/dist-es/src/helpers/MessageListener.js +6 -3
- package/dist-es/src/helpers/TechnicalSupport.js +25 -0
- package/dist-es/types/Json.js +1 -0
- package/dist-types/src/Client.d.ts +2 -0
- package/dist-types/src/__tests__/Client.test.d.ts +1 -0
- package/dist-types/src/__tests__/MessageListener.test.d.ts +1 -0
- package/dist-types/src/helpers/ClientParams.d.ts +1 -0
- package/dist-types/src/helpers/MessageListener.d.ts +2 -2
- package/dist-types/src/helpers/TechnicalSupport.d.ts +9 -0
- package/dist-types/types/Client.d.ts +4 -0
- package/dist-types/types/Event.d.ts +1 -1
- package/dist-types/types/Json.d.ts +7 -0
- package/dist-types/types/Payload.d.ts +4 -0
- package/dist-types/types/index.d.ts +2 -1
- package/package.json +10 -3
package/dist-es/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wildix/xbees-connect",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.7",
|
|
4
4
|
"description": "This library provides easy communication between x-bees and integrated web applications",
|
|
5
5
|
"author": "dimitri.chernykh <dimitri.chernykh@wildix.com>",
|
|
6
6
|
"homepage": "",
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"build:docs": "typedoc",
|
|
18
18
|
"lint": "eslint . && tsc --noEmit",
|
|
19
19
|
"lint:fix": "eslint . --fix",
|
|
20
|
-
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo"
|
|
20
|
+
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"test:watch": "jest --watch",
|
|
23
|
+
"test:coverage": "jest --coverage"
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
23
26
|
"dist-*/**"
|
|
@@ -30,8 +33,12 @@
|
|
|
30
33
|
"access": "public"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
36
|
+
"@types/jest": "^29.5.12",
|
|
33
37
|
"eslint": "^8.55.0",
|
|
38
|
+
"eslint-plugin-jest": "^28.5.0",
|
|
39
|
+
"jest": "^29.7.0",
|
|
34
40
|
"rimraf": "^5.0.5",
|
|
41
|
+
"ts-jest": "^29.1.3",
|
|
35
42
|
"typescript": "^5.3.3"
|
|
36
43
|
},
|
|
37
44
|
"parserOptions": {
|
package/dist-es/src/Client.js
CHANGED
|
@@ -4,6 +4,7 @@ import ClientParams from './helpers/ClientParams';
|
|
|
4
4
|
import LocalStorageManager from './helpers/LocalStorageManager';
|
|
5
5
|
import { MessageListener } from './helpers/MessageListener';
|
|
6
6
|
import PostMessageController from './helpers/PostMessageController';
|
|
7
|
+
import TechnicalSupport from './helpers/TechnicalSupport';
|
|
7
8
|
/**
|
|
8
9
|
* Client provides functionality of communication between xBees and integrated web applications via iFrame or ReactNative WebView
|
|
9
10
|
* integration creates na instance with new Client()
|
|
@@ -128,16 +129,17 @@ export class Client {
|
|
|
128
129
|
return this.sendAsync({ type: ClientEventType.AUTHORIZED });
|
|
129
130
|
}
|
|
130
131
|
addEventListener(eventName, callback) {
|
|
131
|
-
return
|
|
132
|
+
return MessageListener.getInstance().listen(eventName, callback);
|
|
132
133
|
}
|
|
133
134
|
removeEventListener(eventName, callback) {
|
|
134
|
-
|
|
135
|
+
MessageListener.getInstance().off(eventName, callback);
|
|
135
136
|
}
|
|
136
137
|
off(callback) {
|
|
137
138
|
this.localStorageManager.removeOnStorage(callback);
|
|
138
139
|
this.removeEventListener(null, callback);
|
|
139
140
|
}
|
|
140
141
|
onRedirectQuery(callback) {
|
|
142
|
+
console.debug('onRedirectQuery', 'listen');
|
|
141
143
|
return this.addEventListener(EventType.REDIRECT_QUERY, callback);
|
|
142
144
|
}
|
|
143
145
|
onCallEnded(callback) {
|
|
@@ -238,4 +240,7 @@ export class Client {
|
|
|
238
240
|
setIntegrationStorageKey(integrationKey) {
|
|
239
241
|
this.localStorageManager.setIntegrationKey(integrationKey);
|
|
240
242
|
}
|
|
243
|
+
getTechnicalSupport() {
|
|
244
|
+
return TechnicalSupport.getInstance();
|
|
245
|
+
}
|
|
241
246
|
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { Client } from '../Client';
|
|
2
|
+
import { ClientEventType, EventType } from '../enums';
|
|
3
|
+
import ClientParams from '../helpers/ClientParams';
|
|
4
|
+
import LocalStorageManager from '../helpers/LocalStorageManager';
|
|
5
|
+
import { MessageListener } from '../helpers/MessageListener';
|
|
6
|
+
import PostMessageController from '../helpers/PostMessageController';
|
|
7
|
+
import TechnicalSupport from '../helpers/TechnicalSupport';
|
|
8
|
+
jest.mock('../../package.json', () => ({ version: '1.0.0' }));
|
|
9
|
+
jest.mock('../helpers/ClientParams');
|
|
10
|
+
jest.mock('../helpers/LocalStorageManager');
|
|
11
|
+
jest.mock('../helpers/MessageListener');
|
|
12
|
+
jest.mock('../helpers/PostMessageController');
|
|
13
|
+
jest.mock('../helpers/TechnicalSupport');
|
|
14
|
+
describe('Client', () => {
|
|
15
|
+
let clientInstance;
|
|
16
|
+
let originalWindow;
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
originalWindow = { ...globalThis.window };
|
|
19
|
+
});
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
Client.instance = null;
|
|
22
|
+
clientInstance = Client.getInstance();
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
globalThis.window = {
|
|
25
|
+
...originalWindow,
|
|
26
|
+
addEventListener: jest.fn(),
|
|
27
|
+
removeEventListener: jest.fn(),
|
|
28
|
+
location: { host: 'http://example.com' },
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
afterAll(() => {
|
|
32
|
+
globalThis.window = originalWindow;
|
|
33
|
+
});
|
|
34
|
+
it('should return a singleton instance', () => {
|
|
35
|
+
const instance1 = Client.getInstance();
|
|
36
|
+
const instance2 = Client.getInstance();
|
|
37
|
+
expect(instance1).toBe(instance2);
|
|
38
|
+
});
|
|
39
|
+
it('should initialize correctly based on showsUi()', async () => {
|
|
40
|
+
const renderer = jest.fn().mockResolvedValue(undefined);
|
|
41
|
+
jest.spyOn(clientInstance, 'showsUi').mockReturnValue(true);
|
|
42
|
+
Client.initialize(renderer);
|
|
43
|
+
await Promise.resolve();
|
|
44
|
+
expect(renderer).toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
it('should initialize and call ready when showsUi() is false', async () => {
|
|
47
|
+
const renderer = jest.fn().mockResolvedValue(undefined);
|
|
48
|
+
jest.spyOn(clientInstance, 'showsUi').mockReturnValue(false);
|
|
49
|
+
const readySpy = jest.spyOn(clientInstance, 'ready').mockResolvedValue({});
|
|
50
|
+
Client.initialize(renderer);
|
|
51
|
+
await Promise.resolve();
|
|
52
|
+
expect(renderer).not.toHaveBeenCalled();
|
|
53
|
+
expect(readySpy).toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
it('should send a READY message with the correct payload', async () => {
|
|
56
|
+
const sendAsyncSpy = jest
|
|
57
|
+
.spyOn(PostMessageController.getInstance(), 'sendAsync')
|
|
58
|
+
.mockResolvedValue({});
|
|
59
|
+
await clientInstance.ready('web');
|
|
60
|
+
expect(sendAsyncSpy).toHaveBeenCalledWith({
|
|
61
|
+
type: ClientEventType.READY,
|
|
62
|
+
payload: { version: '1.0.0', platform: 'web' },
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it('should return the correct version', () => {
|
|
66
|
+
expect(clientInstance.version()).toBe('1.0.0');
|
|
67
|
+
});
|
|
68
|
+
it('should return isPlatformNative from PostMessageController', () => {
|
|
69
|
+
const isPlatformNativeSpy = jest
|
|
70
|
+
.spyOn(PostMessageController.getInstance(), 'isPlatformNative')
|
|
71
|
+
.mockReturnValue(true);
|
|
72
|
+
expect(clientInstance.isPlatformNative()).toBe(true);
|
|
73
|
+
expect(isPlatformNativeSpy).toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('should return isPlatformWeb from PostMessageController', () => {
|
|
76
|
+
const isPlatformWebSpy = jest.spyOn(PostMessageController.getInstance(), 'isPlatformWeb').mockReturnValue(true);
|
|
77
|
+
expect(clientInstance.isPlatformWeb()).toBe(true);
|
|
78
|
+
expect(isPlatformWebSpy).toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
it('should return isOpenedFromXBees from PostMessageController', () => {
|
|
81
|
+
const isOpenedFromXBeesSpy = jest
|
|
82
|
+
.spyOn(PostMessageController.getInstance(), 'isOpenedFromXBees')
|
|
83
|
+
.mockReturnValue(true);
|
|
84
|
+
expect(clientInstance.isOpenedFromXBees()).toBe(true);
|
|
85
|
+
expect(isOpenedFromXBeesSpy).toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
it('should get user PBX token from ClientParams', () => {
|
|
88
|
+
const userToken = 'token';
|
|
89
|
+
jest.spyOn(ClientParams.getInstance(), 'userToken', 'get').mockReturnValue(userToken);
|
|
90
|
+
expect(clientInstance.getUserPbxToken()).toBe(userToken);
|
|
91
|
+
});
|
|
92
|
+
it('should get user email from ClientParams', () => {
|
|
93
|
+
const userEmail = 'email@example.com';
|
|
94
|
+
jest.spyOn(ClientParams.getInstance(), 'userEmail', 'get').mockReturnValue(userEmail);
|
|
95
|
+
expect(clientInstance.getUserEmail()).toBe(userEmail);
|
|
96
|
+
});
|
|
97
|
+
it('should get referrer from ClientParams', () => {
|
|
98
|
+
const referrer = 'http://example.com';
|
|
99
|
+
jest.spyOn(ClientParams.getInstance(), 'referrer', 'get').mockReturnValue(referrer);
|
|
100
|
+
expect(clientInstance.getReferrer()).toBe(referrer);
|
|
101
|
+
});
|
|
102
|
+
it('should return correct back to app URL for native platform', () => {
|
|
103
|
+
jest.spyOn(ClientParams.getInstance(), 'iframeId', 'get').mockReturnValue('1234');
|
|
104
|
+
jest.spyOn(clientInstance, 'isPlatformNative').mockReturnValue(true);
|
|
105
|
+
expect(clientInstance.getBackToAppUrl()).toBe('com.wildix.rnc://integrations/1234');
|
|
106
|
+
});
|
|
107
|
+
it('should return correct back to app URL for web platform', () => {
|
|
108
|
+
jest.spyOn(ClientParams.getInstance(), 'iframeId', 'get').mockReturnValue('1234');
|
|
109
|
+
jest.spyOn(ClientParams.getInstance(), 'referrer', 'get').mockReturnValue('http://example.com');
|
|
110
|
+
jest.spyOn(clientInstance, 'isPlatformNative').mockReturnValue(false);
|
|
111
|
+
expect(clientInstance.getBackToAppUrl()).toBe('http://example.com/integrations/1234');
|
|
112
|
+
});
|
|
113
|
+
it('should return isDataOnly correctly', () => {
|
|
114
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('no-ui');
|
|
115
|
+
expect(clientInstance.isDataOnly()).toBe(true);
|
|
116
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('daemon');
|
|
117
|
+
expect(clientInstance.isDataOnly()).toBe(true);
|
|
118
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('d');
|
|
119
|
+
expect(clientInstance.isDataOnly()).toBe(false);
|
|
120
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('dialog');
|
|
121
|
+
expect(clientInstance.isDataOnly()).toBe(false);
|
|
122
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('ui');
|
|
123
|
+
expect(clientInstance.isDataOnly()).toBe(false);
|
|
124
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('info-frame');
|
|
125
|
+
expect(clientInstance.isDataOnly()).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
it('should return isSetupDialog correctly', () => {
|
|
128
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('d');
|
|
129
|
+
expect(clientInstance.isSetupDialog()).toBe(true);
|
|
130
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('dialog');
|
|
131
|
+
expect(clientInstance.isSetupDialog()).toBe(true);
|
|
132
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('no-ui');
|
|
133
|
+
expect(clientInstance.isSetupDialog()).toBe(false);
|
|
134
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('daemon');
|
|
135
|
+
expect(clientInstance.isSetupDialog()).toBe(false);
|
|
136
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('ui');
|
|
137
|
+
expect(clientInstance.isDataOnly()).toBe(false);
|
|
138
|
+
jest.spyOn(ClientParams.getInstance(), 'variant', 'get').mockReturnValue('info-frame');
|
|
139
|
+
expect(clientInstance.isDataOnly()).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
it('should return showsUi correctly', () => {
|
|
142
|
+
jest.spyOn(clientInstance, 'isDataOnly').mockReturnValue(false);
|
|
143
|
+
expect(clientInstance.showsUi()).toBe(true);
|
|
144
|
+
jest.spyOn(clientInstance, 'isDataOnly').mockReturnValue(true);
|
|
145
|
+
expect(clientInstance.showsUi()).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
it('should return isActivationOnly correctly', () => {
|
|
148
|
+
jest.spyOn(ClientParams.getInstance(), 'needAuthorize', 'get').mockReturnValue(true);
|
|
149
|
+
expect(clientInstance.isActivationOnly()).toBe(true);
|
|
150
|
+
jest.spyOn(ClientParams.getInstance(), 'needAuthorize', 'get').mockReturnValue(false);
|
|
151
|
+
expect(clientInstance.isActivationOnly()).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
it('should add event listener through MessageListener', () => {
|
|
154
|
+
const eventName = EventType.PBX_TOKEN;
|
|
155
|
+
const callback = jest.fn();
|
|
156
|
+
clientInstance.addEventListener(eventName, callback);
|
|
157
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(eventName, callback);
|
|
158
|
+
});
|
|
159
|
+
it('should remove event listener through MessageListener', () => {
|
|
160
|
+
const eventName = EventType.PBX_TOKEN;
|
|
161
|
+
const callback = jest.fn();
|
|
162
|
+
clientInstance.removeEventListener(eventName, callback);
|
|
163
|
+
expect(MessageListener.getInstance().off).toHaveBeenCalledWith(eventName, callback);
|
|
164
|
+
});
|
|
165
|
+
it('should handle onRedirectQuery correctly', () => {
|
|
166
|
+
const callback = jest.fn();
|
|
167
|
+
clientInstance.onRedirectQuery(callback);
|
|
168
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(EventType.REDIRECT_QUERY, callback);
|
|
169
|
+
});
|
|
170
|
+
it('should handle onCallEnded correctly', () => {
|
|
171
|
+
const callback = jest.fn();
|
|
172
|
+
clientInstance.onCallEnded(callback);
|
|
173
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(EventType.TERMINATE_CALL, callback);
|
|
174
|
+
});
|
|
175
|
+
it('should handle onCallStarted correctly', () => {
|
|
176
|
+
const callback = jest.fn();
|
|
177
|
+
clientInstance.onCallStarted(callback);
|
|
178
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(EventType.ADD_CALL, callback);
|
|
179
|
+
});
|
|
180
|
+
it('should handle onPbxTokenChange correctly', () => {
|
|
181
|
+
const callback = jest.fn();
|
|
182
|
+
clientInstance.onPbxTokenChange(callback);
|
|
183
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(EventType.PBX_TOKEN, callback);
|
|
184
|
+
});
|
|
185
|
+
it('should handle getXBeesToken correctly', async () => {
|
|
186
|
+
const sendAsyncSpy = jest
|
|
187
|
+
.spyOn(PostMessageController.getInstance(), 'sendAsync')
|
|
188
|
+
.mockResolvedValue({});
|
|
189
|
+
await clientInstance.getXBeesToken();
|
|
190
|
+
expect(sendAsyncSpy).toHaveBeenCalledWith({ type: ClientEventType.TOKEN });
|
|
191
|
+
});
|
|
192
|
+
it('should handle onSuggestContacts correctly', () => {
|
|
193
|
+
const callback = jest.fn();
|
|
194
|
+
const addEventListenerSpy = jest.spyOn(clientInstance, 'addEventListener');
|
|
195
|
+
clientInstance.onSuggestContacts(callback);
|
|
196
|
+
expect(PostMessageController.getInstance().sendAsyncErrorSafe).toHaveBeenCalledWith({
|
|
197
|
+
type: ClientEventType.CONTACTS_AUTO_SUGGEST_IS_SUPPORTED,
|
|
198
|
+
});
|
|
199
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(EventType.GET_CONTACTS_AUTO_SUGGEST, expect.any(Function));
|
|
200
|
+
});
|
|
201
|
+
it('should handle onLookupAndMatchContact correctly', () => {
|
|
202
|
+
const callback = jest.fn();
|
|
203
|
+
const addEventListenerSpy = jest.spyOn(clientInstance, 'addEventListener');
|
|
204
|
+
clientInstance.onLookupAndMatchContact(callback);
|
|
205
|
+
expect(PostMessageController.getInstance().sendAsyncErrorSafe).toHaveBeenCalledWith({
|
|
206
|
+
type: ClientEventType.CONTACT_LOOK_UP_AND_MATCH_IS_SUPPORTED,
|
|
207
|
+
});
|
|
208
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(EventType.GET_LOOK_UP_AND_MATCH, expect.any(Function));
|
|
209
|
+
});
|
|
210
|
+
it('should handle onThemeChange correctly', () => {
|
|
211
|
+
const callback = jest.fn();
|
|
212
|
+
clientInstance.onThemeChange(callback);
|
|
213
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(EventType.USE_THEME, callback);
|
|
214
|
+
});
|
|
215
|
+
it('should handle storage correctly', () => {
|
|
216
|
+
const key = 'testKey';
|
|
217
|
+
const value = 'testValue';
|
|
218
|
+
const localStorageManager = LocalStorageManager.getInstance();
|
|
219
|
+
clientInstance.saveToStorage(key, value);
|
|
220
|
+
expect(localStorageManager.save).toHaveBeenCalledWith(key, value);
|
|
221
|
+
clientInstance.deleteFromStorage(key);
|
|
222
|
+
expect(localStorageManager.delete).toHaveBeenCalledWith(key);
|
|
223
|
+
jest.spyOn(localStorageManager, 'retrieve').mockReturnValue(value);
|
|
224
|
+
expect(clientInstance.getFromStorage(key)).toBe(value);
|
|
225
|
+
});
|
|
226
|
+
it('should handle onStorage correctly', () => {
|
|
227
|
+
const callback = jest.fn();
|
|
228
|
+
clientInstance.onStorage(callback);
|
|
229
|
+
expect(LocalStorageManager.getInstance().onStorage).toHaveBeenCalledWith(callback);
|
|
230
|
+
});
|
|
231
|
+
it('should handle onLogout correctly', () => {
|
|
232
|
+
const callback = jest.fn();
|
|
233
|
+
clientInstance.onLogout(callback);
|
|
234
|
+
expect(PostMessageController.getInstance().sendAsync).toHaveBeenCalledWith({
|
|
235
|
+
type: ClientEventType.LOGOUT_IS_SUPPORTED,
|
|
236
|
+
});
|
|
237
|
+
expect(MessageListener.getInstance().listen).toHaveBeenCalledWith(EventType.LOGOUT, callback);
|
|
238
|
+
});
|
|
239
|
+
it('should handle sendAnalytics correctly', () => {
|
|
240
|
+
const eventName = 'testEvent';
|
|
241
|
+
const params = { key: 'value' };
|
|
242
|
+
clientInstance.sendAnalytics(eventName, params);
|
|
243
|
+
expect(PostMessageController.getInstance().sendAsync).toHaveBeenCalledWith({
|
|
244
|
+
type: ClientEventType.SEND_ANALYTICS,
|
|
245
|
+
payload: {
|
|
246
|
+
eventName,
|
|
247
|
+
params,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
it('should set integration storage key correctly', () => {
|
|
252
|
+
const integrationKey = 'integrationKey';
|
|
253
|
+
clientInstance.setIntegrationStorageKey(integrationKey);
|
|
254
|
+
expect(LocalStorageManager.getInstance().setIntegrationKey).toHaveBeenCalledWith(integrationKey);
|
|
255
|
+
});
|
|
256
|
+
it('should get TechnicalSupport instance', () => {
|
|
257
|
+
expect(clientInstance.getTechnicalSupport()).toBe(TechnicalSupport.getInstance());
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { EventType } from '../enums';
|
|
2
|
+
import { MessageListener } from '../helpers/MessageListener';
|
|
3
|
+
describe('MessageListener', () => {
|
|
4
|
+
let listenerInstance;
|
|
5
|
+
let originalWindow;
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
originalWindow = { ...globalThis.window };
|
|
8
|
+
});
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
MessageListener.instance = null;
|
|
11
|
+
listenerInstance = MessageListener.getInstance();
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
// (listenerInstance as any).listeners = [];
|
|
14
|
+
// (listenerInstance as any).useSubscription = false;
|
|
15
|
+
globalThis.window = {
|
|
16
|
+
...originalWindow,
|
|
17
|
+
addEventListener: jest.fn(),
|
|
18
|
+
removeEventListener: jest.fn(),
|
|
19
|
+
location: { host: 'http://example.com' },
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
globalThis.window = originalWindow;
|
|
24
|
+
});
|
|
25
|
+
it('should return a singleton instance', () => {
|
|
26
|
+
const instance1 = MessageListener.getInstance();
|
|
27
|
+
const instance2 = MessageListener.getInstance();
|
|
28
|
+
expect(instance1).toBe(instance2);
|
|
29
|
+
});
|
|
30
|
+
it('should add an event listener and call the callback on message event', () => {
|
|
31
|
+
const mockCallback = jest.fn();
|
|
32
|
+
listenerInstance.listen(EventType.REDIRECT_QUERY, mockCallback);
|
|
33
|
+
// console.log((listenerInstance as any).listeners);
|
|
34
|
+
const messageEvent = {
|
|
35
|
+
data: JSON.stringify({ type: EventType.REDIRECT_QUERY, payload: { some: 'data' } }),
|
|
36
|
+
origin: 'http://parent.example.com',
|
|
37
|
+
source: {},
|
|
38
|
+
};
|
|
39
|
+
listenerInstance.onMessage(messageEvent);
|
|
40
|
+
expect(mockCallback).toHaveBeenCalledWith({ some: 'data' });
|
|
41
|
+
});
|
|
42
|
+
it('should not call the callback if the message origin is the same as the current window location', () => {
|
|
43
|
+
const mockCallback = jest.fn();
|
|
44
|
+
listenerInstance.listen(EventType.ADD_CALL, mockCallback);
|
|
45
|
+
const messageEvent = {
|
|
46
|
+
data: JSON.stringify({ type: EventType.ADD_CALL, payload: { some: 'data' } }),
|
|
47
|
+
origin: window.location.host,
|
|
48
|
+
source: window,
|
|
49
|
+
};
|
|
50
|
+
listenerInstance.onMessage(messageEvent);
|
|
51
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
it('should remove an event listener and stop calling the callback on message event', () => {
|
|
54
|
+
const mockCallback = jest.fn();
|
|
55
|
+
const removeEventListener = listenerInstance.listen(EventType.ADD_CALL, mockCallback);
|
|
56
|
+
removeEventListener();
|
|
57
|
+
const messageEvent = {
|
|
58
|
+
data: JSON.stringify({ type: EventType.ADD_CALL, payload: { some: 'data' } }),
|
|
59
|
+
origin: 'http://example.com',
|
|
60
|
+
source: window,
|
|
61
|
+
};
|
|
62
|
+
listenerInstance.onMessage(messageEvent);
|
|
63
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
it('should not add duplicate event listeners', () => {
|
|
66
|
+
const mockCallback = jest.fn();
|
|
67
|
+
listenerInstance.listen(EventType.ADD_CALL, mockCallback);
|
|
68
|
+
listenerInstance.listen(EventType.ADD_CALL, mockCallback);
|
|
69
|
+
expect(listenerInstance.listeners.length).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
it('should parse valid JSON message data correctly', () => {
|
|
72
|
+
const validJsonMessage = '{"type": "xBeesAddCall", "payload": {"some": "data"}}';
|
|
73
|
+
const parsedMessage = listenerInstance.parseJSON(validJsonMessage);
|
|
74
|
+
expect(parsedMessage).toEqual({ type: EventType.ADD_CALL, payload: { some: 'data' } });
|
|
75
|
+
});
|
|
76
|
+
it('should return null for invalid JSON message data', () => {
|
|
77
|
+
const invalidJsonMessage = 'invalid json';
|
|
78
|
+
const parsedMessage = listenerInstance.parseJSON(invalidJsonMessage);
|
|
79
|
+
expect(parsedMessage).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
it('should handle non-JSON message data gracefully', () => {
|
|
82
|
+
const messageEvent = {
|
|
83
|
+
data: 'invalid json',
|
|
84
|
+
origin: 'http://example.com',
|
|
85
|
+
source: window,
|
|
86
|
+
};
|
|
87
|
+
expect(listenerInstance.parseMessage(messageEvent)).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
it('should correctly manage the useSubscription flag and event listeners on window', () => {
|
|
90
|
+
const mockCallback = jest.fn();
|
|
91
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
92
|
+
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
|
93
|
+
const removeEventListener = listenerInstance.listen(EventType.ADD_CALL, mockCallback);
|
|
94
|
+
expect(listenerInstance.useSubscription).toBe(true);
|
|
95
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
|
|
96
|
+
removeEventListener();
|
|
97
|
+
expect(listenerInstance.useSubscription).toBe(false);
|
|
98
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -23,4 +23,7 @@ export default class ClientParams {
|
|
|
23
23
|
this.referrer = (params.get(UrlParams.REFERRER) ?? params.get(DeprecatedUrlParams.REFERRER));
|
|
24
24
|
this.needAuthorize = params.has(UrlParams.AUTHORIZE) ?? params.has(DeprecatedUrlParams.AUTHORIZE);
|
|
25
25
|
}
|
|
26
|
+
toString() {
|
|
27
|
+
return JSON.stringify(this);
|
|
28
|
+
}
|
|
26
29
|
}
|
|
@@ -33,14 +33,17 @@ export class MessageListener {
|
|
|
33
33
|
}
|
|
34
34
|
onMessage = (message) => {
|
|
35
35
|
if (window.location.host === message.origin || window === message.source) {
|
|
36
|
+
console.debug('onMessage skipped', 'skipped', 'window.location.host', window.location.host, message.origin, 'window', window === message.source, message);
|
|
36
37
|
// skip events started from integration itself if any
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
39
40
|
const data = this.parseMessage(message);
|
|
40
41
|
if (!data) {
|
|
42
|
+
console.debug('onMessage skipped', message);
|
|
41
43
|
return;
|
|
42
44
|
}
|
|
43
45
|
const { type, payload } = data;
|
|
46
|
+
console.debug('onMessage call', type, payload);
|
|
44
47
|
this.listeners.forEach(({ eventName, callback }) => {
|
|
45
48
|
if (eventName === type) {
|
|
46
49
|
if (type === EventType.ADD_CALL) {
|
|
@@ -55,7 +58,7 @@ export class MessageListener {
|
|
|
55
58
|
}
|
|
56
59
|
});
|
|
57
60
|
};
|
|
58
|
-
|
|
61
|
+
listen(eventName, callback) {
|
|
59
62
|
if (!this.useSubscription) {
|
|
60
63
|
this.useSubscription = true;
|
|
61
64
|
window.addEventListener('message', this.onMessage);
|
|
@@ -65,10 +68,10 @@ export class MessageListener {
|
|
|
65
68
|
this.listeners.push({ eventName, callback });
|
|
66
69
|
}
|
|
67
70
|
return () => {
|
|
68
|
-
this.
|
|
71
|
+
this.off(eventName, callback);
|
|
69
72
|
};
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
off(eventName, callback) {
|
|
72
75
|
this.listeners = this.listeners.filter(({ eventName: _eventName, callback: _callback }) => !(Object.is(callback, _callback) && (!eventName ? true : eventName === _eventName)));
|
|
73
76
|
if (this.useSubscription && !this.listeners.length) {
|
|
74
77
|
this.useSubscription = false;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ClientEventType } from '../enums';
|
|
2
|
+
import ClientParams from './ClientParams';
|
|
3
|
+
import PostMessageController from './PostMessageController';
|
|
4
|
+
class TechnicalSupport {
|
|
5
|
+
static instance = null;
|
|
6
|
+
static getInstance() {
|
|
7
|
+
if (!this.instance) {
|
|
8
|
+
this.instance = new TechnicalSupport();
|
|
9
|
+
}
|
|
10
|
+
return this.instance;
|
|
11
|
+
}
|
|
12
|
+
// eslint-disable-next-line
|
|
13
|
+
constructor() { }
|
|
14
|
+
sendTechnicalInformation(message, data) {
|
|
15
|
+
return PostMessageController.getInstance().sendAsyncErrorSafe({
|
|
16
|
+
type: ClientEventType.SEND_TECHNICAL_SUPPORT_INFORMATION,
|
|
17
|
+
payload: {
|
|
18
|
+
'x-iframe-params': ClientParams.getInstance(),
|
|
19
|
+
message,
|
|
20
|
+
data,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export default TechnicalSupport;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -8,6 +8,7 @@ import { LookupAndMatchContactsResolver, Reject, SuggestContactsResolver } from
|
|
|
8
8
|
import { StorageEventCallback } from '../types/Storage';
|
|
9
9
|
import { ToastSeverity } from '../types/Toast';
|
|
10
10
|
import { ClientEventType, EventType } from './enums';
|
|
11
|
+
import TechnicalSupport from './helpers/TechnicalSupport';
|
|
11
12
|
/**
|
|
12
13
|
* Client provides functionality of communication between xBees and integrated web applications via iFrame or ReactNative WebView
|
|
13
14
|
* integration creates na instance with new Client()
|
|
@@ -65,4 +66,5 @@ export declare class Client implements ConnectClient {
|
|
|
65
66
|
onLogout(callback: Callback<EventType.LOGOUT>): RemoveEventListener;
|
|
66
67
|
sendAnalytics(eventName: string, params?: Record<string, string>): void;
|
|
67
68
|
setIntegrationStorageKey(integrationKey: string): void;
|
|
69
|
+
getTechnicalSupport(): TechnicalSupport;
|
|
68
70
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -10,6 +10,6 @@ export declare class MessageListener {
|
|
|
10
10
|
private parseMessage;
|
|
11
11
|
private parseJSON;
|
|
12
12
|
private onMessage;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
listen<T extends EventType = EventType>(eventName: T, callback: Callback<T>): RemoveEventListener;
|
|
14
|
+
off<T extends EventType = EventType>(eventName: T | null, callback: Callback<T>): void;
|
|
15
15
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { JSONArray, JSONObject } from '../../types';
|
|
2
|
+
import { ClientEventType } from '../enums';
|
|
3
|
+
declare class TechnicalSupport {
|
|
4
|
+
private static instance;
|
|
5
|
+
static getInstance(): TechnicalSupport;
|
|
6
|
+
private constructor();
|
|
7
|
+
sendTechnicalInformation(message: string, data?: JSONObject | JSONArray): Promise<import("../../types/Message").ResponseMessage<ClientEventType.SEND_TECHNICAL_SUPPORT_INFORMATION> | undefined>;
|
|
8
|
+
}
|
|
9
|
+
export default TechnicalSupport;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ClientEventType, EventType } from '../src/enums';
|
|
2
|
+
import TechnicalSupport from '../src/helpers/TechnicalSupport';
|
|
2
3
|
import { Callback } from './Callback';
|
|
3
4
|
import { Contact, ContactQuery } from './Contact';
|
|
4
5
|
import { RemoveEventListener } from './Listener';
|
|
@@ -147,4 +148,7 @@ export interface ConnectClient {
|
|
|
147
148
|
/**
|
|
148
149
|
* send analytics data to x-bees for track into analytics data */
|
|
149
150
|
sendAnalytics: (eventName: string, params?: Record<string, string>) => void;
|
|
151
|
+
/**
|
|
152
|
+
* special object to provide x-bees technical support */
|
|
153
|
+
getTechnicalSupport: () => TechnicalSupport;
|
|
150
154
|
}
|
|
@@ -19,4 +19,4 @@ export type EventCallbackMap = {
|
|
|
19
19
|
[EventType.PBX_TOKEN]: (token: EventPayloadMap[EventType.PBX_TOKEN]) => void;
|
|
20
20
|
[EventType.REDIRECT_QUERY]: (query: EventPayloadMap[EventType.REDIRECT_QUERY]) => void;
|
|
21
21
|
};
|
|
22
|
-
export type RawMessageEvent = MessageEvent<
|
|
22
|
+
export type RawMessageEvent = MessageEvent<string | Message>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import ClientParams from '../src/helpers/ClientParams';
|
|
1
2
|
import { Contact, ContactQuery } from './Contact';
|
|
2
3
|
import { Conversation } from './conversation';
|
|
4
|
+
import { JSONArray, JSONObject } from './Json';
|
|
3
5
|
import { SupportedPlatformVariant } from './Platform';
|
|
4
6
|
import { ToastSeverity } from './Toast';
|
|
5
7
|
export interface IPayloadViewPort {
|
|
@@ -52,5 +54,7 @@ export interface IPayloadSendAnalytics {
|
|
|
52
54
|
params?: Record<string, string>;
|
|
53
55
|
}
|
|
54
56
|
export interface IPayloadTechSupport {
|
|
57
|
+
'x-iframe-params': ClientParams;
|
|
55
58
|
message: string;
|
|
59
|
+
data?: JSONObject | JSONArray;
|
|
56
60
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Contact, ContactQuery } from './Contact';
|
|
2
|
+
import { JSONArray, JSONObject } from './Json';
|
|
2
3
|
import { IPayloadAutoSuggestResult, IPayloadCallStart, IPayloadCallStartedInfo, IPayloadContactMatchResult, IPayloadContactMatchResultNotFound, IPayloadContactResult, IPayloadContextResult, IPayloadConversationResult, IPayloadSendAnalytics, IPayloadTechSupport, IPayloadThemeChange, IPayloadToast, IPayloadVersion, IPayloadViewPort } from './Payload';
|
|
3
|
-
export type { Contact, ContactQuery, IPayloadAutoSuggestResult, IPayloadCallStart, IPayloadCallStartedInfo, IPayloadContactMatchResult, IPayloadContactMatchResultNotFound, IPayloadContactResult, IPayloadContextResult, IPayloadConversationResult, IPayloadSendAnalytics, IPayloadTechSupport, IPayloadThemeChange, IPayloadToast, IPayloadVersion, IPayloadViewPort, };
|
|
4
|
+
export type { Contact, ContactQuery, IPayloadAutoSuggestResult, IPayloadCallStart, IPayloadCallStartedInfo, IPayloadContactMatchResult, IPayloadContactMatchResultNotFound, IPayloadContactResult, IPayloadContextResult, IPayloadConversationResult, IPayloadSendAnalytics, IPayloadTechSupport, IPayloadThemeChange, IPayloadToast, IPayloadVersion, IPayloadViewPort, JSONArray, JSONObject, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wildix/xbees-connect",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.7",
|
|
4
4
|
"description": "This library provides easy communication between x-bees and integrated web applications",
|
|
5
5
|
"author": "dimitri.chernykh <dimitri.chernykh@wildix.com>",
|
|
6
6
|
"homepage": "",
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"build:docs": "typedoc",
|
|
18
18
|
"lint": "eslint . && tsc --noEmit",
|
|
19
19
|
"lint:fix": "eslint . --fix",
|
|
20
|
-
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo"
|
|
20
|
+
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"test:watch": "jest --watch",
|
|
23
|
+
"test:coverage": "jest --coverage"
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
23
26
|
"dist-*/**"
|
|
@@ -30,8 +33,12 @@
|
|
|
30
33
|
"access": "public"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
36
|
+
"@types/jest": "^29.5.12",
|
|
33
37
|
"eslint": "^8.55.0",
|
|
38
|
+
"eslint-plugin-jest": "^28.5.0",
|
|
39
|
+
"jest": "^29.7.0",
|
|
34
40
|
"rimraf": "^5.0.5",
|
|
41
|
+
"ts-jest": "^29.1.3",
|
|
35
42
|
"typescript": "^5.3.3"
|
|
36
43
|
},
|
|
37
44
|
"parserOptions": {
|
|
@@ -42,5 +49,5 @@
|
|
|
42
49
|
"engines": {
|
|
43
50
|
"node": ">=16"
|
|
44
51
|
},
|
|
45
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "8c0d507e1063a5c4de57b59fc58dcba65a14d6d6"
|
|
46
53
|
}
|