kuzzle 2.20.3 → 2.21.1

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.
@@ -4,23 +4,10 @@ import { NativeController } from "./baseController";
4
4
  * @class DebugController
5
5
  */
6
6
  export declare class DebugController extends NativeController {
7
- private inspector;
8
- private debuggerStatus;
9
- /**
10
- * Map<eventName, Set<connectionId>>
11
- */
12
- private events;
13
- /**
14
- * Map of functions from the DebugModules
15
- */
16
- private kuzzlePostMethods;
7
+ constructor();
17
8
  /**
18
- * List of DebugModule for DebugController
19
- * Used to add new methods and events to the protocol
9
+ * Return the node version of the current Kuzzle instance
20
10
  */
21
- private modules;
22
- constructor();
23
- init(): Promise<void>;
24
11
  nodeVersion(): Promise<string>;
25
12
  /**
26
13
  * Connect the debugger
@@ -44,16 +31,4 @@ export declare class DebugController extends NativeController {
44
31
  * Remove the websocket connection from the events' listeners
45
32
  */
46
33
  removeListener(request: KuzzleRequest): Promise<void>;
47
- /**
48
- * Execute a method using the Chrome Debug Protocol
49
- * @param method Chrome Debug Protocol method to execute
50
- * @param params
51
- * @returns
52
- */
53
- private inspectorPost;
54
- /**
55
- * Sends a direct notification to a websocket connection without having to listen to a specific room
56
- */
57
- private notifyConnection;
58
- private notifyGlobalListeners;
59
34
  }
@@ -48,10 +48,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
49
  exports.DebugController = void 0;
50
50
  const baseController_1 = require("./baseController");
51
- const inspector_1 = __importDefault(require("inspector"));
52
51
  const kerror = __importStar(require("../../kerror"));
53
52
  const get_1 = __importDefault(require("lodash/get"));
54
- const DEBUGGER_EVENT = "kuzzle-debugger-event";
55
53
  /**
56
54
  * @class DebugController
57
55
  */
@@ -65,53 +63,10 @@ class DebugController extends baseController_1.NativeController {
65
63
  "addListener",
66
64
  "removeListener",
67
65
  ]);
68
- this.debuggerStatus = false;
69
- /**
70
- * Map<eventName, Set<connectionId>>
71
- */
72
- this.events = new Map();
73
- /**
74
- * Map of functions from the DebugModules
75
- */
76
- this.kuzzlePostMethods = new Map();
77
- /**
78
- * List of DebugModule for DebugController
79
- * Used to add new methods and events to the protocol
80
- */
81
- this.modules = [];
82
- }
83
- async init() {
84
- super.init();
85
- this.inspector = new inspector_1.default.Session();
86
- // Remove connection id from the list of listeners for each event
87
- global.kuzzle.on("connection:remove", (connectionId) => {
88
- if (!this.debuggerStatus) {
89
- return;
90
- }
91
- for (const listener of this.events.values()) {
92
- listener.delete(connectionId);
93
- }
94
- });
95
- this.inspector.on("inspectorNotification", async (payload) => {
96
- if (!this.debuggerStatus) {
97
- return;
98
- }
99
- await this.notifyGlobalListeners(payload.method, payload);
100
- const listeners = this.events.get(payload.method);
101
- if (!listeners) {
102
- return;
103
- }
104
- const promises = [];
105
- for (const connectionId of listeners) {
106
- promises.push(this.notifyConnection(connectionId, DEBUGGER_EVENT, {
107
- event: payload.method,
108
- result: payload,
109
- }));
110
- }
111
- // No need to catch, notify is already try-catched
112
- await Promise.all(promises);
113
- });
114
66
  }
67
+ /**
68
+ * Return the node version of the current Kuzzle instance
69
+ */
115
70
  async nodeVersion() {
116
71
  return process.version;
117
72
  }
@@ -119,168 +74,58 @@ class DebugController extends baseController_1.NativeController {
119
74
  * Connect the debugger
120
75
  */
121
76
  async enable() {
122
- if (this.debuggerStatus) {
123
- return;
124
- }
125
- this.inspector.connect();
126
- this.debuggerStatus = true;
127
- for (const module of this.modules) {
128
- await module.init(this.inspector);
129
- for (const methodName of module.methods) {
130
- if (!module[methodName]) {
131
- throw new Error(`Missing implementation of method "${methodName}" inside DebugModule "${module.name}"`);
132
- }
133
- this.kuzzlePostMethods.set(`Kuzzle.${module.name}.${methodName}`, module[methodName].bind(module));
134
- }
135
- for (const eventName of module.events) {
136
- module.on(eventName, async (payload) => {
137
- if (!this.debuggerStatus) {
138
- return;
139
- }
140
- const event = `Kuzzle.${module.name}.${eventName}`;
141
- await this.notifyGlobalListeners(event, payload);
142
- const listeners = this.events.get(event);
143
- if (!listeners) {
144
- return;
145
- }
146
- const promises = [];
147
- for (const connectionId of listeners) {
148
- promises.push(this.notifyConnection(connectionId, DEBUGGER_EVENT, {
149
- event,
150
- result: payload,
151
- }));
152
- }
153
- // No need to catch, notify is already try-catched
154
- await Promise.all(promises);
155
- });
156
- }
77
+ if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
78
+ throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
157
79
  }
80
+ await global.kuzzle.ask("core:debugger:enable");
158
81
  }
159
82
  /**
160
83
  * Disconnect the debugger and clears all the events listeners
161
84
  */
162
85
  async disable() {
163
- if (!this.debuggerStatus) {
164
- return;
165
- }
166
- for (const module of this.modules) {
167
- for (const eventName of module.events) {
168
- module.removeAllListeners(eventName);
169
- }
170
- await module.cleanup();
86
+ if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
87
+ throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
171
88
  }
172
- this.inspector.disconnect();
173
- this.debuggerStatus = false;
174
- this.events.clear();
175
- this.kuzzlePostMethods.clear();
89
+ await global.kuzzle.ask("core:debugger:disable");
176
90
  }
177
91
  /**
178
92
  * Trigger action from debugger directly following the Chrome Debug Protocol
179
93
  * See: https://chromedevtools.github.io/devtools-protocol/v8/
180
94
  */
181
95
  async post(request) {
182
- if (!this.debuggerStatus) {
183
- throw kerror.get("core", "debugger", "not_enabled");
184
- }
185
- const method = request.getBodyString("method");
186
- const params = request.getBodyObject("params", {});
187
- if (method.startsWith("Kuzzle.")) {
188
- const debugModuleMethod = this.kuzzlePostMethods.get(method);
189
- if (debugModuleMethod) {
190
- return debugModuleMethod(params);
191
- }
192
- throw kerror.get("core", "debugger", "method_not_found", method);
193
- }
194
96
  if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
195
97
  throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
196
98
  }
197
- return this.inspectorPost(method, params);
99
+ const method = request.getBodyString("method");
100
+ const params = request.getBodyObject("params", {});
101
+ return global.kuzzle.ask("core:debugger:post", method, params);
198
102
  }
199
103
  /**
200
104
  * Make the websocket connection listen and receive events from Chrome Debug Protocol
201
105
  * See events from: https://chromedevtools.github.io/devtools-protocol/v8/
202
106
  */
203
107
  async addListener(request) {
108
+ if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
109
+ throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
110
+ }
204
111
  if (request.context.connection.protocol !== "websocket") {
205
112
  throw kerror.get("api", "assert", "unsupported_protocol", request.context.connection.protocol, "debug:addListener");
206
113
  }
207
- if (!this.debuggerStatus) {
208
- throw kerror.get("core", "debugger", "not_enabled");
209
- }
210
114
  const event = request.getBodyString("event");
211
- let listeners = this.events.get(event);
212
- if (!listeners) {
213
- listeners = new Set();
214
- this.events.set(event, listeners);
215
- }
216
- listeners.add(request.context.connection.id);
115
+ await global.kuzzle.ask("core:debugger:addListener", event, request.context.connection.id);
217
116
  }
218
117
  /**
219
118
  * Remove the websocket connection from the events' listeners
220
119
  */
221
120
  async removeListener(request) {
121
+ if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
122
+ throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
123
+ }
222
124
  if (request.context.connection.protocol !== "websocket") {
223
125
  throw kerror.get("api", "assert", "unsupported_protocol", request.context.connection.protocol, "debug:removeListener");
224
126
  }
225
- if (!this.debuggerStatus) {
226
- throw kerror.get("core", "debugger", "not_enabled");
227
- }
228
127
  const event = request.getBodyString("event");
229
- const listeners = this.events.get(event);
230
- if (listeners) {
231
- listeners.delete(request.context.connection.id);
232
- }
233
- }
234
- /**
235
- * Execute a method using the Chrome Debug Protocol
236
- * @param method Chrome Debug Protocol method to execute
237
- * @param params
238
- * @returns
239
- */
240
- async inspectorPost(method, params) {
241
- if (!this.debuggerStatus) {
242
- throw kerror.get("core", "debugger", "not_enabled");
243
- }
244
- let resolve;
245
- const promise = new Promise((res) => {
246
- resolve = res;
247
- });
248
- this.inspector.post(method, params, (err, res) => {
249
- if (err) {
250
- resolve({
251
- error: JSON.stringify(Object.getOwnPropertyDescriptors(err)),
252
- });
253
- }
254
- else {
255
- resolve(res);
256
- }
257
- });
258
- return promise;
259
- }
260
- /**
261
- * Sends a direct notification to a websocket connection without having to listen to a specific room
262
- */
263
- async notifyConnection(connectionId, event, payload) {
264
- global.kuzzle.entryPoint._notify({
265
- channels: [event],
266
- connectionId,
267
- payload,
268
- });
269
- }
270
- async notifyGlobalListeners(event, payload) {
271
- const listeners = this.events.get("*");
272
- if (!listeners) {
273
- return;
274
- }
275
- const promises = [];
276
- for (const connectionId of listeners) {
277
- promises.push(this.notifyConnection(connectionId, DEBUGGER_EVENT, {
278
- event,
279
- result: payload,
280
- }));
281
- }
282
- // No need to catch, notify is already try-catched
283
- await Promise.all(promises);
128
+ await global.kuzzle.ask("core:debugger:removeListener", event, request.context.connection.id);
284
129
  }
285
130
  }
286
131
  exports.DebugController = DebugController;
@@ -390,12 +390,34 @@ class DocumentController extends NativeController {
390
390
 
391
391
  const validated = await global.kuzzle.validation.validate(request, false);
392
392
 
393
+ // Add metadata
394
+ const pipeMetadataResult = await this.pipe(
395
+ "generic:document:injectMetadata",
396
+ {
397
+ metadata: {
398
+ author: userId,
399
+ createdAt: Date.now(),
400
+ updatedAt: null,
401
+ updater: null,
402
+ },
403
+ request,
404
+ }
405
+ );
406
+
393
407
  const created = await this.ask(
394
408
  "core:storage:public:document:create",
395
409
  index,
396
410
  collection,
397
- validated.getBody(),
398
- { id, refresh, userId }
411
+ {
412
+ ...validated.getBody(),
413
+ _kuzzle_info: pipeMetadataResult.metadata,
414
+ },
415
+ {
416
+ id,
417
+ injectKuzzleMeta: false,
418
+ refresh,
419
+ userId,
420
+ }
399
421
  );
400
422
 
401
423
  if (!silent) {
@@ -440,13 +462,34 @@ class DocumentController extends NativeController {
440
462
  false
441
463
  );
442
464
 
465
+ // Add metadata
466
+ const pipeMetadataResult = await this.pipe(
467
+ "generic:document:injectMetadata",
468
+ {
469
+ metadata: {
470
+ author: userId,
471
+ createdAt: Date.now(),
472
+ updatedAt: Date.now(),
473
+ updater: userId,
474
+ },
475
+ request,
476
+ }
477
+ );
478
+
443
479
  const response = await this.ask(
444
480
  "core:storage:public:document:createOrReplace",
445
481
  index,
446
482
  collection,
447
483
  id,
448
- content,
449
- { refresh, userId }
484
+ {
485
+ ...content,
486
+ _kuzzle_info: pipeMetadataResult.metadata,
487
+ },
488
+ {
489
+ injectKuzzleMeta: false,
490
+ refresh,
491
+ userId,
492
+ }
450
493
  );
451
494
 
452
495
  if (!silent) {
@@ -493,13 +536,33 @@ class DocumentController extends NativeController {
493
536
  false
494
537
  );
495
538
 
539
+ // Add metadata
540
+ const pipeMetadataResult = await this.pipe(
541
+ "generic:document:injectMetadata",
542
+ {
543
+ metadata: {
544
+ updatedAt: Date.now(),
545
+ updater: userId,
546
+ },
547
+ request,
548
+ }
549
+ );
550
+
496
551
  const updatedDocument = await this.ask(
497
552
  "core:storage:public:document:update",
498
553
  index,
499
554
  collection,
500
555
  id,
501
- content,
502
- { refresh, retryOnConflict, userId }
556
+ {
557
+ ...content,
558
+ _kuzzle_info: pipeMetadataResult.metadata,
559
+ },
560
+ {
561
+ injectKuzzleMeta: false,
562
+ refresh,
563
+ retryOnConflict,
564
+ userId,
565
+ }
503
566
  );
504
567
 
505
568
  const _updatedFields = extractFields(content, {
@@ -548,13 +611,41 @@ class DocumentController extends NativeController {
548
611
  const source = request.getBoolean("source");
549
612
  const { index, collection } = request.getIndexAndCollection();
550
613
 
614
+ // Add metadata
615
+ const pipeMetadataResult = await this.pipe(
616
+ "generic:document:injectMetadata",
617
+ {
618
+ defaultMetadata: {
619
+ author: userId,
620
+ createdAt: Date.now(),
621
+ },
622
+ metadata: {
623
+ updatedAt: Date.now(),
624
+ updater: userId,
625
+ },
626
+ request,
627
+ }
628
+ );
629
+
551
630
  const updatedDocument = await this.ask(
552
631
  "core:storage:public:document:upsert",
553
632
  index,
554
633
  collection,
555
634
  id,
556
- content,
557
- { defaultValues, refresh, retryOnConflict, userId }
635
+ {
636
+ ...content,
637
+ _kuzzle_info: pipeMetadataResult.metadata,
638
+ },
639
+ {
640
+ defaultValues: {
641
+ ...defaultValues,
642
+ _kuzzle_info: pipeMetadataResult.defaultMetadata,
643
+ },
644
+ injectKuzzleMeta: false,
645
+ refresh,
646
+ retryOnConflict,
647
+ userId,
648
+ }
558
649
  );
559
650
 
560
651
  if (!silent && updatedDocument.created) {
@@ -633,13 +724,34 @@ class DocumentController extends NativeController {
633
724
  false
634
725
  );
635
726
 
727
+ // Add metadata
728
+ const pipeMetadataResult = await this.pipe(
729
+ "generic:document:injectMetadata",
730
+ {
731
+ metadata: {
732
+ author: userId,
733
+ createdAt: Date.now(),
734
+ updatedAt: Date.now(),
735
+ updater: userId,
736
+ },
737
+ request,
738
+ }
739
+ );
740
+
636
741
  const response = await this.ask(
637
742
  "core:storage:public:document:replace",
638
743
  index,
639
744
  collection,
640
745
  id,
641
- content,
642
- { refresh, userId }
746
+ {
747
+ ...content,
748
+ _kuzzle_info: pipeMetadataResult.metadata,
749
+ },
750
+ {
751
+ injectKuzzleMeta: false,
752
+ refresh,
753
+ userId,
754
+ }
643
755
  );
644
756
 
645
757
  if (!silent) {
package/lib/api/funnel.js CHANGED
@@ -51,6 +51,7 @@ const debug = require("../util/debug")("kuzzle:funnel");
51
51
  const processError = kerror.wrap("api", "process");
52
52
  const { has } = require("../util/safeObject");
53
53
  const { HttpStream } = require("../types");
54
+ const get = require("lodash/get");
54
55
 
55
56
  // Actions of the auth controller that does not necessite to verify the token
56
57
  // when cookie auth is active
@@ -179,6 +180,12 @@ class Funnel {
179
180
  throw processError.get("not_enough_nodes");
180
181
  }
181
182
 
183
+ const isRequestFromDebugSession = get(
184
+ request,
185
+ "context.connection.misc.internal.debugSession",
186
+ false
187
+ );
188
+
182
189
  if (this.overloaded) {
183
190
  const now = Date.now();
184
191
 
@@ -226,7 +233,8 @@ class Funnel {
226
233
  */
227
234
  if (
228
235
  this.pendingRequestsQueue.length >=
229
- global.kuzzle.config.limits.requestsBufferSize
236
+ global.kuzzle.config.limits.requestsBufferSize &&
237
+ !isRequestFromDebugSession
230
238
  ) {
231
239
  const error = processError.get("overloaded");
232
240
  global.kuzzle.emit("log:error", error);
@@ -239,7 +247,13 @@ class Funnel {
239
247
  request.internalId,
240
248
  new PendingRequest(request, fn, context)
241
249
  );
242
- this.pendingRequestsQueue.push(request.internalId);
250
+
251
+ if (isRequestFromDebugSession) {
252
+ // Push at the front to prioritize debug requests
253
+ this.pendingRequestsQueue.unshift(request.internalId);
254
+ } else {
255
+ this.pendingRequestsQueue.push(request.internalId);
256
+ }
243
257
 
244
258
  if (!this.overloaded) {
245
259
  this.overloaded = true;
@@ -135,6 +135,14 @@ export declare class KuzzleRequest {
135
135
  status: number;
136
136
  timestamp: number;
137
137
  };
138
+ /**
139
+ * Return the requested controller
140
+ */
141
+ getController(): string;
142
+ /**
143
+ * Returns the requested controller's action
144
+ */
145
+ getAction(): string;
138
146
  /**
139
147
  * Returns the `lang` param of the request.
140
148
  *
@@ -324,6 +324,18 @@ class KuzzleRequest {
324
324
  timestamp: this.timestamp,
325
325
  };
326
326
  }
327
+ /**
328
+ * Return the requested controller
329
+ */
330
+ getController() {
331
+ return this[_input].controller;
332
+ }
333
+ /**
334
+ * Returns the requested controller's action
335
+ */
336
+ getAction() {
337
+ return this[_input].action;
338
+ }
327
339
  /**
328
340
  * Returns the `lang` param of the request.
329
341
  *
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.ClusterIdCardHandler = exports.IdCard = void 0;
27
27
  const name_generator_1 = require("../util/name-generator");
28
- const worker_threads_1 = require("worker_threads");
28
+ const child_process_1 = require("child_process");
29
29
  const bluebird_1 = __importDefault(require("bluebird"));
30
30
  require("../types");
31
31
  const REDIS_PREFIX = "{cluster/node}/";
@@ -110,14 +110,16 @@ class ClusterIdCardHandler {
110
110
  } while (!reserved);
111
111
  await this.addIdCardToIndex();
112
112
  this.refreshWorker = this.constructWorker(`${__dirname}/workers/IDCardRenewer.js`);
113
- this.refreshWorker.unref();
114
113
  this.refreshWorker.on("message", async (message) => {
115
114
  if (message.error) {
116
115
  await this.node.evictSelf(message.error);
117
116
  }
118
117
  });
118
+ this.refreshWorker.on("close", () => {
119
+ this.disposed = true;
120
+ });
119
121
  // Transfer informations to the worker
120
- this.refreshWorker.postMessage({
122
+ this.refreshWorker.send({
121
123
  action: "start",
122
124
  kuzzle: {
123
125
  config: global.kuzzle.config,
@@ -138,7 +140,7 @@ class ClusterIdCardHandler {
138
140
  * Helper method to mock worker instantiation in unit tests
139
141
  */
140
142
  constructWorker(path) {
141
- return new worker_threads_1.Worker(path);
143
+ return (0, child_process_1.fork)(path);
142
144
  }
143
145
  /**
144
146
  * Start refreshing the ID Card before the worker starts to ensure the ID Card
@@ -163,9 +165,20 @@ class ClusterIdCardHandler {
163
165
  });
164
166
  }
165
167
  async dispose() {
168
+ if (this.disposed) {
169
+ return;
170
+ }
166
171
  this.disposed = true;
167
- if (this.refreshWorker) {
168
- this.refreshWorker.postMessage({ action: "dispose" });
172
+ if (this.refreshWorker &&
173
+ this.refreshWorker.connected &&
174
+ !this.refreshWorker.killed &&
175
+ this.refreshWorker.channel) {
176
+ try {
177
+ this.refreshWorker.send({ action: "dispose" });
178
+ }
179
+ catch (e) {
180
+ // It could happens that the worker has been killed before the dispose causing send to fail
181
+ }
169
182
  }
170
183
  }
171
184
  /**
@@ -249,6 +249,24 @@ class ClusterNode {
249
249
  this.command.dispose();
250
250
  }
251
251
 
252
+ /**
253
+ * Notify other nodes to not evict this node
254
+ *
255
+ * @param {bool} evictionPrevented
256
+ */
257
+ preventEviction(evictionPrevented) {
258
+ this.publisher.sendNodePreventEviction(evictionPrevented);
259
+ // This node is subscribed to the other node and might not receive their heartbeat while debugging
260
+ // so this node should not have the responsability of evicting others when his own eviction is prevented
261
+ // when debugging.
262
+ // Otherwise when recovering from a debug session, all the other nodes will be evicted.
263
+ for (const subscriber of this.remoteNodes.values()) {
264
+ subscriber.handleNodePreventEviction({
265
+ evictionPrevented,
266
+ });
267
+ }
268
+ }
269
+
252
270
  /**
253
271
  * Adds a new remote node, and subscribes to it.
254
272
  * @param {string} id - remote node ID
@@ -678,6 +696,10 @@ class ClusterNode {
678
696
  * Registers ask events
679
697
  */
680
698
  registerAskEvents() {
699
+ global.kuzzle.onAsk("cluster:node:preventEviction", (state) => {
700
+ this.preventEviction(state);
701
+ });
702
+
681
703
  /**
682
704
  * Removes a room from the full state, and only for this node.
683
705
  * Removes the room from Koncorde if, and only if, no other node uses it.
@@ -816,7 +838,11 @@ class ClusterNode {
816
838
  this.onAuthStrategyRemoved(name, pluginName)
817
839
  );
818
840
 
819
- global.kuzzle.on("admin:afterDump", (suffix) => this.onDumpRequest(suffix));
841
+ global.kuzzle.on("admin:afterDump", (request) => {
842
+ const suffix = request.getString("suffix", "manual-api-action");
843
+
844
+ return this.onDumpRequest(suffix);
845
+ });
820
846
 
821
847
  global.kuzzle.on("admin:afterResetSecurity", () => this.onSecurityReset());
822
848
 
@@ -21,6 +21,11 @@ message NodeShutdown {
21
21
  string nodeId = 2;
22
22
  }
23
23
 
24
+ message NodePreventEviction {
25
+ uint64 messageId = 1;
26
+ bool evictionPrevented = 2;
27
+ }
28
+
24
29
  message NewRealtimeRoom {
25
30
  uint64 messageId = 1;
26
31
  string index = 2;