1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Kuzzle = void 0;
|
4 | const KuzzleEventEmitter_1 = require("./core/KuzzleEventEmitter");
|
5 | const Auth_1 = require("./controllers/Auth");
|
6 | const Bulk_1 = require("./controllers/Bulk");
|
7 | const Collection_1 = require("./controllers/Collection");
|
8 | const Document_1 = require("./controllers/Document");
|
9 | const Index_1 = require("./controllers/Index");
|
10 | const Realtime_1 = require("./controllers/Realtime");
|
11 | const Server_1 = require("./controllers/Server");
|
12 | const Security_1 = require("./controllers/Security");
|
13 | const MemoryStorage_1 = require("./controllers/MemoryStorage");
|
14 | const uuidv4_1 = require("./utils/uuidv4");
|
15 | const proxify_1 = require("./utils/proxify");
|
16 | const events = [
|
17 | 'connected',
|
18 | 'discarded',
|
19 | 'disconnected',
|
20 | 'loginAttempt',
|
21 | 'networkError',
|
22 | 'offlineQueuePush',
|
23 | 'offlineQueuePop',
|
24 | 'queryError',
|
25 | 'reconnected',
|
26 | 'tokenExpired'
|
27 | ];
|
28 | class Kuzzle extends KuzzleEventEmitter_1.KuzzleEventEmitter {
|
29 | |
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | constructor(
|
41 | /**
|
42 | * Network protocol to connect to Kuzzle. (e.g. `Http` or `WebSocket`)
|
43 | */
|
44 | protocol, options = {}) {
|
45 | super();
|
46 | if (protocol === undefined || protocol === null) {
|
47 | throw new Error('"protocol" argument missing');
|
48 | }
|
49 |
|
50 | for (const method of ['addListener', 'isReady', 'query']) {
|
51 | if (typeof protocol[method] !== 'function') {
|
52 | throw new Error(`Protocol instance must implement a "${method}" method`);
|
53 | }
|
54 | }
|
55 | this.protocol = protocol;
|
56 | this._protectedEvents = {
|
57 | connected: {},
|
58 | error: {},
|
59 | disconnected: {},
|
60 | reconnected: {},
|
61 | tokenExpired: {},
|
62 | loginAttempt: {}
|
63 | };
|
64 | this.autoResubscribe = typeof options.autoResubscribe === 'boolean'
|
65 | ? options.autoResubscribe
|
66 | : true;
|
67 | this.eventTimeout = typeof options.eventTimeout === 'number'
|
68 | ? options.eventTimeout
|
69 | : 200;
|
70 | this.sdkVersion = typeof SDKVERSION === 'undefined'
|
71 |
|
72 | ? require('../package').version
|
73 | : SDKVERSION;
|
74 | this.sdkName = `js@${this.sdkVersion}`;
|
75 | this.volatile = typeof options.volatile === 'object'
|
76 | ? options.volatile
|
77 | : {};
|
78 |
|
79 | this.useController(Auth_1.AuthController, 'auth');
|
80 | this.useController(Bulk_1.BulkController, 'bulk');
|
81 | this.useController(Collection_1.CollectionController, 'collection');
|
82 | this.useController(Document_1.DocumentController, 'document');
|
83 | this.useController(Index_1.IndexController, 'index');
|
84 | this.useController(MemoryStorage_1.MemoryStorageController, 'ms');
|
85 | this.useController(Realtime_1.RealtimeController, 'realtime');
|
86 | this.useController(Security_1.SecurityController, 'security');
|
87 | this.useController(Server_1.ServerController, 'server');
|
88 |
|
89 | this._offlineQueue = [];
|
90 | this._autoQueue = typeof options.autoQueue === 'boolean'
|
91 | ? options.autoQueue
|
92 | : false;
|
93 | this._autoReplay = typeof options.autoReplay === 'boolean'
|
94 | ? options.autoReplay
|
95 | : false;
|
96 | this._offlineQueueLoader = typeof options.offlineQueueLoader === 'function'
|
97 | ? options.offlineQueueLoader
|
98 | : null;
|
99 | this._queueFilter = typeof options.queueFilter === 'function'
|
100 | ? options.queueFilter
|
101 | : null;
|
102 | this._queueMaxSize = typeof options.queueMaxSize === 'number'
|
103 | ? options.queueMaxSize
|
104 | : 500;
|
105 | this._queueTTL = typeof options.queueTTL === 'number'
|
106 | ? options.queueTTL
|
107 | : 120000;
|
108 | this._replayInterval = typeof options.replayInterval === 'number'
|
109 | ? options.replayInterval
|
110 | : 10;
|
111 | this._tokenExpiredInterval = typeof options.tokenExpiredInterval === 'number'
|
112 | ? options.tokenExpiredInterval
|
113 | : 1000;
|
114 | if (options.offlineMode === 'auto') {
|
115 | this._autoQueue = true;
|
116 | this._autoReplay = true;
|
117 | }
|
118 | this._queuing = false;
|
119 | this._lastTokenExpired = null;
|
120 | return proxify_1.proxify(this, {
|
121 | seal: true,
|
122 | name: 'kuzzle',
|
123 | exposeApi: true
|
124 | });
|
125 | }
|
126 | get authenticated() {
|
127 | return this.auth.authenticationToken && !this.auth.authenticationToken.expired;
|
128 | }
|
129 | get autoQueue() {
|
130 | return this._autoQueue;
|
131 | }
|
132 | set autoQueue(value) {
|
133 | this._checkPropertyType('_autoQueue', 'boolean', value);
|
134 | this._autoQueue = value;
|
135 | }
|
136 | get autoReconnect() {
|
137 | return this.protocol.autoReconnect;
|
138 | }
|
139 | set autoReconnect(value) {
|
140 | this._checkPropertyType('autoReconnect', 'boolean', value);
|
141 | this.protocol.autoReconnect = value;
|
142 | }
|
143 | get autoReplay() {
|
144 | return this._autoReplay;
|
145 | }
|
146 | set autoReplay(value) {
|
147 | this._checkPropertyType('_autoReplay', 'boolean', value);
|
148 | this._autoReplay = value;
|
149 | }
|
150 | get connected() {
|
151 | return this.protocol.connected;
|
152 | }
|
153 | get host() {
|
154 | return this.protocol.host;
|
155 | }
|
156 | get jwt() {
|
157 | if (!this.auth.authenticationToken) {
|
158 | return null;
|
159 | }
|
160 | return this.auth.authenticationToken.encodedJwt;
|
161 | }
|
162 | set jwt(encodedJwt) {
|
163 | this.auth.authenticationToken = encodedJwt;
|
164 | }
|
165 | get offlineQueue() {
|
166 | return this._offlineQueue;
|
167 | }
|
168 | get offlineQueueLoader() {
|
169 | return this._offlineQueueLoader;
|
170 | }
|
171 | set offlineQueueLoader(value) {
|
172 | this._checkPropertyType('_offlineQueueLoader', 'function', value);
|
173 | this._offlineQueueLoader = value;
|
174 | }
|
175 | get port() {
|
176 | return this.protocol.port;
|
177 | }
|
178 | get queueFilter() {
|
179 | return this._queueFilter;
|
180 | }
|
181 | set queueFilter(value) {
|
182 | this._checkPropertyType('_queueFilter', 'function', value);
|
183 | this._queueFilter = value;
|
184 | }
|
185 | get queueMaxSize() {
|
186 | return this._queueMaxSize;
|
187 | }
|
188 | set queueMaxSize(value) {
|
189 | this._checkPropertyType('_queueMaxSize', 'number', value);
|
190 | this._queueMaxSize = value;
|
191 | }
|
192 | get queueTTL() {
|
193 | return this._queueTTL;
|
194 | }
|
195 | set queueTTL(value) {
|
196 | this._checkPropertyType('_queueTTL', 'number', value);
|
197 | this._queueTTL = value;
|
198 | }
|
199 | get reconnectionDelay() {
|
200 | return this.protocol.reconnectionDelay;
|
201 | }
|
202 | get replayInterval() {
|
203 | return this._replayInterval;
|
204 | }
|
205 | set replayInterval(value) {
|
206 | this._checkPropertyType('_replayInterval', 'number', value);
|
207 | this._replayInterval = value;
|
208 | }
|
209 | get sslConnection() {
|
210 | return this.protocol.sslConnection;
|
211 | }
|
212 | get tokenExpiredInterval() {
|
213 | return this._tokenExpiredInterval;
|
214 | }
|
215 | set tokenExpiredInterval(value) {
|
216 | this._checkPropertyType('_tokenExpiredInterval', 'number', value);
|
217 | this._tokenExpiredInterval = value;
|
218 | }
|
219 | |
220 |
|
221 |
|
222 |
|
223 | emit(eventName, ...payload) {
|
224 | const now = Date.now(), protectedEvent = this._protectedEvents[eventName];
|
225 | if (protectedEvent) {
|
226 | if (protectedEvent.lastEmitted
|
227 | && protectedEvent.lastEmitted > now - this.eventTimeout) {
|
228 | return false;
|
229 | }
|
230 | protectedEvent.lastEmitted = now;
|
231 | }
|
232 | return this._superEmit(eventName, ...payload);
|
233 | }
|
234 | _superEmit(eventName, ...payload) {
|
235 | return super.emit(eventName, ...payload);
|
236 | }
|
237 | |
238 |
|
239 |
|
240 | connect() {
|
241 | if (this.protocol.isReady()) {
|
242 | return Promise.resolve();
|
243 | }
|
244 | if (this.autoQueue) {
|
245 | this.startQueuing();
|
246 | }
|
247 | this.protocol.addListener('queryError', (err, query) => this.emit('queryError', err, query));
|
248 | this.protocol.addListener('tokenExpired', () => this.tokenExpired());
|
249 | this.protocol.addListener('connect', () => {
|
250 | if (this.autoQueue) {
|
251 | this.stopQueuing();
|
252 | }
|
253 | if (this.autoReplay) {
|
254 | this.playQueue();
|
255 | }
|
256 | this.emit('connected');
|
257 | });
|
258 | this.protocol.addListener('networkError', error => {
|
259 | if (this.autoQueue) {
|
260 | this.startQueuing();
|
261 | }
|
262 | this.emit('networkError', error);
|
263 | });
|
264 | this.protocol.addListener('disconnect', () => {
|
265 | this.emit('disconnected');
|
266 | });
|
267 | this.protocol.addListener('reconnect', () => {
|
268 | if (this.autoQueue) {
|
269 | this.stopQueuing();
|
270 | }
|
271 | if (this.autoReplay) {
|
272 | this.playQueue();
|
273 | }
|
274 | if (this.auth.authenticationToken) {
|
275 | return this.auth.checkToken()
|
276 | .then(res => {
|
277 |
|
278 | if (!res.valid) {
|
279 | this.auth.authenticationToken = null;
|
280 | }
|
281 | })
|
282 | .catch(() => {
|
283 | this.auth.authenticationToken = null;
|
284 | })
|
285 | .then(() => this.emit('reconnected'));
|
286 | }
|
287 | this.emit('reconnected');
|
288 | });
|
289 | this.protocol.addListener('discarded', data => this.emit('discarded', data));
|
290 | return this.protocol.connect();
|
291 | }
|
292 | |
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 | addListener(event, listener) {
|
300 | if (events.indexOf(event) === -1) {
|
301 | throw new Error(`[${event}] is not a known event. Known events: ${events.toString()}`);
|
302 | }
|
303 | return this._superAddListener(event, listener);
|
304 | }
|
305 | _superAddListener(event, listener) {
|
306 | return super.addListener(event, listener);
|
307 | }
|
308 | |
309 |
|
310 |
|
311 |
|
312 |
|
313 | flushQueue() {
|
314 | this._offlineQueue = [];
|
315 | return this;
|
316 | }
|
317 | |
318 |
|
319 |
|
320 | disconnect() {
|
321 | this.protocol.close();
|
322 | }
|
323 | |
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 | query(request = {}, options = {}) {
|
336 | if (typeof request !== 'object' || Array.isArray(request)) {
|
337 | throw new Error(`Kuzzle.query: Invalid request: ${JSON.stringify(request)}`);
|
338 | }
|
339 | if (typeof options !== 'object' || Array.isArray(options)) {
|
340 | throw new Error(`Kuzzle.query: Invalid "options" argument: ${JSON.stringify(options)}`);
|
341 | }
|
342 | if (!request.requestId) {
|
343 | request.requestId = uuidv4_1.uuidv4();
|
344 | }
|
345 | if (typeof request.refresh === 'undefined' && typeof options.refresh !== 'undefined') {
|
346 | request.refresh = options.refresh;
|
347 | }
|
348 | if (!request.volatile) {
|
349 | request.volatile = this.volatile;
|
350 | }
|
351 | else if (typeof request.volatile !== 'object'
|
352 | || Array.isArray(request.volatile)) {
|
353 | throw new Error(`Kuzzle.query: Invalid volatile argument received: ${JSON.stringify(request.volatile)}`);
|
354 | }
|
355 | for (const item of Object.keys(this.volatile)) {
|
356 | if (request.volatile[item] === undefined) {
|
357 | request.volatile[item] = this.volatile[item];
|
358 | }
|
359 | }
|
360 | request.volatile.sdkInstanceId = request.volatile.sdkInstanceId || this.protocol.id;
|
361 | request.volatile.sdkName = request.volatile.sdkName || this.sdkName;
|
362 | this.auth.authenticateRequest(request);
|
363 | let queuable = true;
|
364 | if (options && options.queuable === false) {
|
365 | queuable = false;
|
366 | }
|
367 | if (this.queueFilter) {
|
368 | queuable = queuable && this.queueFilter(request);
|
369 | }
|
370 | if (this._queuing) {
|
371 | if (queuable) {
|
372 | this._cleanQueue();
|
373 | this.emit('offlineQueuePush', { request });
|
374 | return new Promise((resolve, reject) => {
|
375 | this.offlineQueue.push({
|
376 | resolve,
|
377 | reject,
|
378 | request,
|
379 | ts: Date.now()
|
380 | });
|
381 | });
|
382 | }
|
383 | this.emit('discarded', { request });
|
384 | return Promise.reject(new Error(`Unable to execute request: not connected to a Kuzzle server.
|
385 | Discarded request: ${JSON.stringify(request)}`));
|
386 | }
|
387 | return this.protocol.query(request, options);
|
388 | }
|
389 | |
390 |
|
391 |
|
392 | startQueuing() {
|
393 | this._queuing = true;
|
394 | return this;
|
395 | }
|
396 | |
397 |
|
398 |
|
399 | stopQueuing() {
|
400 | this._queuing = false;
|
401 | return this;
|
402 | }
|
403 | |
404 |
|
405 |
|
406 | playQueue() {
|
407 | if (this.protocol.isReady()) {
|
408 | this._cleanQueue();
|
409 | this._dequeue();
|
410 | }
|
411 | return this;
|
412 | }
|
413 | |
414 |
|
415 |
|
416 |
|
417 | tokenExpired() {
|
418 | const now = Date.now();
|
419 | if ((now - this._lastTokenExpired) < this.tokenExpiredInterval) {
|
420 |
|
421 | return;
|
422 | }
|
423 | this._lastTokenExpired = now;
|
424 | this.emit('tokenExpired');
|
425 | }
|
426 | |
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 | useController(ControllerClass, accessor) {
|
433 | if (!(accessor && accessor.length > 0)) {
|
434 | throw new Error('You must provide a valid accessor.');
|
435 | }
|
436 | if (this.__proxy__ ? this.__proxy__.hasProp(accessor) : this[accessor]) {
|
437 | throw new Error(`There is already a controller with the accessor '${accessor}'. Please use another one.`);
|
438 | }
|
439 | const controller = new ControllerClass(this);
|
440 | if (!(controller.name && controller.name.length > 0)) {
|
441 | throw new Error('Controllers must have a name.');
|
442 | }
|
443 | if (controller.kuzzle !== this) {
|
444 | throw new Error('You must pass the Kuzzle SDK instance to the parent constructor.');
|
445 | }
|
446 | if (this.__proxy__) {
|
447 | this.__proxy__.registerProp(accessor);
|
448 | }
|
449 | this[accessor] = controller;
|
450 | return this;
|
451 | }
|
452 | _checkPropertyType(prop, typestr, value) {
|
453 | const wrongType = typestr === 'array' ? !Array.isArray(value) : typeof value !== typestr;
|
454 | if (wrongType) {
|
455 | throw new Error(`Expected ${prop} to be a ${typestr}, ${typeof value} received`);
|
456 | }
|
457 | }
|
458 | |
459 |
|
460 |
|
461 | _cleanQueue() {
|
462 | const now = Date.now();
|
463 | let lastDocumentIndex = -1;
|
464 | if (this.queueTTL > 0) {
|
465 | this.offlineQueue.forEach((query, index) => {
|
466 | if (query.ts < now - this.queueTTL) {
|
467 | lastDocumentIndex = index;
|
468 | }
|
469 | });
|
470 | if (lastDocumentIndex !== -1) {
|
471 | this.offlineQueue
|
472 | .splice(0, lastDocumentIndex + 1)
|
473 | .forEach(droppedRequest => {
|
474 | this.emit('offlineQueuePop', droppedRequest.query);
|
475 | });
|
476 | }
|
477 | }
|
478 | if (this.queueMaxSize > 0 && this.offlineQueue.length > this.queueMaxSize) {
|
479 | this.offlineQueue
|
480 | .splice(0, this.offlineQueue.length - this.queueMaxSize)
|
481 | .forEach(droppedRequest => {
|
482 | this.emit('offlineQueuePop', droppedRequest.query);
|
483 | });
|
484 | }
|
485 | }
|
486 | |
487 |
|
488 |
|
489 | _dequeue() {
|
490 | const uniqueQueue = {}, dequeuingProcess = () => {
|
491 | if (this.offlineQueue.length > 0) {
|
492 | this.protocol.query(this.offlineQueue[0].request)
|
493 | .then(this.offlineQueue[0].resolve)
|
494 | .catch(this.offlineQueue[0].reject);
|
495 | this.emit('offlineQueuePop', this.offlineQueue.shift());
|
496 | setTimeout(() => {
|
497 | dequeuingProcess();
|
498 | }, Math.max(0, this.replayInterval));
|
499 | }
|
500 | };
|
501 | if (this.offlineQueueLoader) {
|
502 | if (typeof this.offlineQueueLoader !== 'function') {
|
503 | throw new Error('Invalid value for offlineQueueLoader property. Expected: function. Got: ' + typeof this.offlineQueueLoader);
|
504 | }
|
505 | return Promise.resolve()
|
506 | .then(() => this.offlineQueueLoader())
|
507 | .then(additionalQueue => {
|
508 | if (Array.isArray(additionalQueue)) {
|
509 | this._offlineQueue = additionalQueue
|
510 | .concat(this.offlineQueue)
|
511 | .filter(query => {
|
512 |
|
513 | if (!query.request || query.request.requestId === undefined || !query.request.action || !query.request.controller) {
|
514 | throw new Error('Invalid offline queue request. One or more missing properties: requestId, action, controller.');
|
515 | }
|
516 | return Object.prototype.hasOwnProperty.call(uniqueQueue, query.request.requestId)
|
517 | ? false
|
518 | : (uniqueQueue[query.request.requestId] = true);
|
519 | });
|
520 | dequeuingProcess();
|
521 | }
|
522 | else {
|
523 | throw new Error('Invalid value returned by the offlineQueueLoader function. Expected: array. Got: ' + typeof additionalQueue);
|
524 | }
|
525 | });
|
526 | }
|
527 | dequeuingProcess();
|
528 | }
|
529 | }
|
530 | exports.Kuzzle = Kuzzle;
|
531 |
|
\ | No newline at end of file |