kuzzle 2.20.3 → 2.21.0

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
  }
@@ -42,16 +42,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
42
42
  __setModuleDefault(result, mod);
43
43
  return result;
44
44
  };
45
- var __importDefault = (this && this.__importDefault) || function (mod) {
46
- return (mod && mod.__esModule) ? mod : { "default": mod };
47
- };
48
45
  Object.defineProperty(exports, "__esModule", { value: true });
49
46
  exports.DebugController = void 0;
50
47
  const baseController_1 = require("./baseController");
51
- const inspector_1 = __importDefault(require("inspector"));
52
48
  const kerror = __importStar(require("../../kerror"));
53
- const get_1 = __importDefault(require("lodash/get"));
54
- const DEBUGGER_EVENT = "kuzzle-debugger-event";
55
49
  /**
56
50
  * @class DebugController
57
51
  */
@@ -65,53 +59,10 @@ class DebugController extends baseController_1.NativeController {
65
59
  "addListener",
66
60
  "removeListener",
67
61
  ]);
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
62
  }
63
+ /**
64
+ * Return the node version of the current Kuzzle instance
65
+ */
115
66
  async nodeVersion() {
116
67
  return process.version;
117
68
  }
@@ -119,82 +70,22 @@ class DebugController extends baseController_1.NativeController {
119
70
  * Connect the debugger
120
71
  */
121
72
  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
- }
157
- }
73
+ await global.kuzzle.ask("core:debugger:enable");
158
74
  }
159
75
  /**
160
76
  * Disconnect the debugger and clears all the events listeners
161
77
  */
162
78
  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();
171
- }
172
- this.inspector.disconnect();
173
- this.debuggerStatus = false;
174
- this.events.clear();
175
- this.kuzzlePostMethods.clear();
79
+ await global.kuzzle.ask("core:debugger:disable");
176
80
  }
177
81
  /**
178
82
  * Trigger action from debugger directly following the Chrome Debug Protocol
179
83
  * See: https://chromedevtools.github.io/devtools-protocol/v8/
180
84
  */
181
85
  async post(request) {
182
- if (!this.debuggerStatus) {
183
- throw kerror.get("core", "debugger", "not_enabled");
184
- }
185
86
  const method = request.getBodyString("method");
186
87
  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
- if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
195
- throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
196
- }
197
- return this.inspectorPost(method, params);
88
+ return global.kuzzle.ask("core:debugger:post", method, params);
198
89
  }
199
90
  /**
200
91
  * Make the websocket connection listen and receive events from Chrome Debug Protocol
@@ -204,16 +95,8 @@ class DebugController extends baseController_1.NativeController {
204
95
  if (request.context.connection.protocol !== "websocket") {
205
96
  throw kerror.get("api", "assert", "unsupported_protocol", request.context.connection.protocol, "debug:addListener");
206
97
  }
207
- if (!this.debuggerStatus) {
208
- throw kerror.get("core", "debugger", "not_enabled");
209
- }
210
98
  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);
99
+ await global.kuzzle.ask("core:debugger:addListener", event, request.context.connection.id);
217
100
  }
218
101
  /**
219
102
  * Remove the websocket connection from the events' listeners
@@ -222,65 +105,8 @@ class DebugController extends baseController_1.NativeController {
222
105
  if (request.context.connection.protocol !== "websocket") {
223
106
  throw kerror.get("api", "assert", "unsupported_protocol", request.context.connection.protocol, "debug:removeListener");
224
107
  }
225
- if (!this.debuggerStatus) {
226
- throw kerror.get("core", "debugger", "not_enabled");
227
- }
228
108
  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);
109
+ await global.kuzzle.ask("core:debugger:removeListener", event, request.context.connection.id);
284
110
  }
285
111
  }
286
112
  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) {
@@ -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,15 @@ 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
+ }
260
+
252
261
  /**
253
262
  * Adds a new remote node, and subscribes to it.
254
263
  * @param {string} id - remote node ID
@@ -678,6 +687,10 @@ class ClusterNode {
678
687
  * Registers ask events
679
688
  */
680
689
  registerAskEvents() {
690
+ global.kuzzle.onAsk("cluster:node:preventEviction", (state) => {
691
+ this.preventEviction(state);
692
+ });
693
+
681
694
  /**
682
695
  * Removes a room from the full state, and only for this node.
683
696
  * Removes the room from Koncorde if, and only if, no other node uses it.
@@ -816,7 +829,11 @@ class ClusterNode {
816
829
  this.onAuthStrategyRemoved(name, pluginName)
817
830
  );
818
831
 
819
- global.kuzzle.on("admin:afterDump", (suffix) => this.onDumpRequest(suffix));
832
+ global.kuzzle.on("admin:afterDump", (request) => {
833
+ const suffix = request.getString("suffix", "manual-api-action");
834
+
835
+ return this.onDumpRequest(suffix);
836
+ });
820
837
 
821
838
  global.kuzzle.on("admin:afterResetSecurity", () => this.onSecurityReset());
822
839
 
@@ -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;
@@ -270,6 +270,16 @@ class ClusterPublisher {
270
270
  return this.send("NodeShutdown", { nodeId });
271
271
  }
272
272
 
273
+ /**
274
+ * Publishes an event about the node being in debug mode
275
+ * @param {bool} evictionPrevented
276
+ *
277
+ * @returns {Long} ID of the message sent
278
+ */
279
+ sendNodePreventEviction(evictionPrevented) {
280
+ return this.send("NodePreventEviction", { evictionPrevented });
281
+ }
282
+
273
283
  /**
274
284
  * Publishes an event about a node being evicted
275
285
  *
@@ -55,6 +55,8 @@ class ClusterSubscriber {
55
55
  this.remoteNodeIP = remoteNodeIP;
56
56
  this.remoteNodeAddress = `tcp://${remoteNodeIP}:${this.localNode.config.ports.sync}`;
57
57
  this.remoteNodeId = remoteNodeId;
58
+ // Used in debug mode when the node might be slower
59
+ this.remoteNodeEvictionPrevented = false;
58
60
  this.socket = null;
59
61
  this.protoroot = null;
60
62
 
@@ -87,6 +89,7 @@ class ClusterSubscriber {
87
89
  NewAuthStrategy: this.handleNewAuthStrategy,
88
90
  NewRealtimeRoom: this.handleNewRealtimeRoom,
89
91
  NodeEvicted: this.handleNodeEviction,
92
+ NodePreventEviction: this.handleNodePreventEviction,
90
93
  NodeShutdown: this.handleNodeShutdown,
91
94
  RefreshIndexCache: this.handleRefreshIndexCache,
92
95
  RefreshValidators: this.handleRefreshValidators,
@@ -229,6 +232,10 @@ class ClusterSubscriber {
229
232
  }
230
233
  }
231
234
 
235
+ async handleNodePreventEviction(message) {
236
+ this.remoteNodeEvictionPrevented = message.evictionPrevented;
237
+ }
238
+
232
239
  /**
233
240
  * Handles a heartbeat from the remote node
234
241
  *
@@ -673,7 +680,7 @@ class ClusterSubscriber {
673
680
  * to recover, otherwise we evict it from the cluster.
674
681
  */
675
682
  async checkHeartbeat() {
676
- if (this.state === stateEnum.EVICTED) {
683
+ if (this.state === stateEnum.EVICTED || this.remoteNodeEvictionPrevented) {
677
684
  return;
678
685
  }
679
686
 
@@ -1,11 +1,9 @@
1
1
  "use strict";
2
2
 
3
- const { isMainThread, parentPort } = require("worker_threads");
4
3
  const Redis = require("../../service/cache/redis");
5
4
 
6
5
  class IDCardRenewer {
7
6
  constructor() {
8
- this.parentPort = parentPort;
9
7
  this.redis = null;
10
8
  this.refreshTimer = null;
11
9
  this.nodeIdKey = null;
@@ -50,7 +48,7 @@ class IDCardRenewer {
50
48
  }
51
49
 
52
50
  // Notify that the worker is running and updating the ID Card
53
- this.parentPort.postMessage({ initialized: true });
51
+ process.send({ initialized: true });
54
52
  }
55
53
 
56
54
  async initRedis(config, name) {
@@ -73,13 +71,13 @@ class IDCardRenewer {
73
71
  // => this node is too slow, we need to remove it from the cluster
74
72
  if (refreshed === 0) {
75
73
  await this.dispose();
76
- this.parentPort.postMessage({
74
+ process.send({
77
75
  error: "Node too slow: ID card expired",
78
76
  });
79
77
  }
80
78
  } catch (error) {
81
79
  await this.dispose();
82
- this.parentPort.postMessage({
80
+ process.send({
83
81
  error: `Failed to refresh ID Card: ${error.message}`,
84
82
  });
85
83
  }
@@ -112,26 +110,25 @@ class IDCardRenewer {
112
110
  }
113
111
  }
114
112
 
115
- if (!isMainThread) {
116
- const idCardRenewer = new IDCardRenewer();
117
-
118
- parentPort.on("message", async (message) => {
119
- if (message.action === "start") {
120
- // Simulate basic global Kuzzle Context
121
- global.kuzzle = { ...message.kuzzle };
122
- global.kuzzle.log = {
123
- debug: console.debug, // eslint-disable-line no-console
124
- error: console.error, // eslint-disable-line no-console
125
- info: console.info, // eslint-disable-line no-console
126
- warn: console.warn, // eslint-disable-line no-console
127
- };
128
- // Should never throw
129
- await idCardRenewer.init(message);
130
- } else if (message.action === "dispose") {
131
- // Should never throw
132
- await idCardRenewer.dispose();
133
- }
134
- });
135
- }
113
+ const idCardRenewer = new IDCardRenewer();
114
+
115
+ process.on("message", async (message) => {
116
+ if (message.action === "start") {
117
+ // Simulate basic global Kuzzle Context
118
+ global.kuzzle = { ...message.kuzzle };
119
+ global.kuzzle.log = {
120
+ debug: console.debug, // eslint-disable-line no-console
121
+ error: console.error, // eslint-disable-line no-console
122
+ info: console.info, // eslint-disable-line no-console
123
+ warn: console.warn, // eslint-disable-line no-console
124
+ };
125
+ // Should never throw
126
+ await idCardRenewer.init(message);
127
+ } else if (message.action === "dispose") {
128
+ // Should never throw
129
+ await idCardRenewer.dispose();
130
+ process.exit(0);
131
+ }
132
+ });
136
133
 
137
134
  module.exports = { IDCardRenewer };
@@ -0,0 +1,45 @@
1
+ import { JSONObject } from "kuzzle-sdk";
2
+ export declare class KuzzleDebugger {
3
+ private inspector;
4
+ private debuggerStatus;
5
+ /**
6
+ * Map<eventName, Set<connectionId>>
7
+ */
8
+ private events;
9
+ init(): Promise<void>;
10
+ registerAsks(): Promise<void>;
11
+ /**
12
+ * Connect the debugger
13
+ */
14
+ enable(): Promise<void>;
15
+ /**
16
+ * Disconnect the debugger and clears all the events listeners
17
+ */
18
+ disable(): Promise<void>;
19
+ /**
20
+ * Trigger action from debugger directly following the Chrome Debug Protocol
21
+ * See: https://chromedevtools.github.io/devtools-protocol/v8/
22
+ */
23
+ post(method: string, params?: JSONObject): Promise<JSONObject>;
24
+ /**
25
+ * Make the websocket connection listen and receive events from Chrome Debug Protocol
26
+ * See events from: https://chromedevtools.github.io/devtools-protocol/v8/
27
+ */
28
+ addListener(event: string, connectionId: string): Promise<void>;
29
+ /**
30
+ * Remove the websocket connection from the events" listeners
31
+ */
32
+ removeListener(event: string, connectionId: string): Promise<void>;
33
+ /**
34
+ * Execute a method using the Chrome Debug Protocol
35
+ * @param method Chrome Debug Protocol method to execute
36
+ * @param params
37
+ * @returns
38
+ */
39
+ private inspectorPost;
40
+ /**
41
+ * Sends a direct notification to a websocket connection without having to listen to a specific room
42
+ */
43
+ private notifyConnection;
44
+ private notifyGlobalListeners;
45
+ }
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.KuzzleDebugger = void 0;
30
+ const inspector_1 = __importDefault(require("inspector"));
31
+ const kerror = __importStar(require("../../kerror"));
32
+ const get_1 = __importDefault(require("lodash/get"));
33
+ const DEBUGGER_EVENT = "kuzzle-debugger-event";
34
+ class KuzzleDebugger {
35
+ constructor() {
36
+ this.debuggerStatus = false;
37
+ /**
38
+ * Map<eventName, Set<connectionId>>
39
+ */
40
+ this.events = new Map();
41
+ }
42
+ async init() {
43
+ this.inspector = new inspector_1.default.Session();
44
+ // Remove connection id from the list of listeners for each event
45
+ global.kuzzle.on("connection:remove", (connectionId) => {
46
+ if (!this.debuggerStatus) {
47
+ return;
48
+ }
49
+ for (const listener of this.events.values()) {
50
+ listener.delete(connectionId);
51
+ }
52
+ });
53
+ this.inspector.on("inspectorNotification", async (payload) => {
54
+ if (!this.debuggerStatus) {
55
+ return;
56
+ }
57
+ await this.notifyGlobalListeners(payload.method, payload);
58
+ const listeners = this.events.get(payload.method);
59
+ if (!listeners) {
60
+ return;
61
+ }
62
+ const promises = [];
63
+ for (const connectionId of listeners) {
64
+ promises.push(this.notifyConnection(connectionId, DEBUGGER_EVENT, {
65
+ event: payload.method,
66
+ result: payload,
67
+ }));
68
+ }
69
+ // No need to catch, notify is already try-catched
70
+ await Promise.all(promises);
71
+ });
72
+ await this.registerAsks();
73
+ }
74
+ async registerAsks() {
75
+ global.kuzzle.onAsk("core:debugger:enable", () => this.enable());
76
+ global.kuzzle.onAsk("core:debugger:disable", () => this.disable());
77
+ global.kuzzle.onAsk("core:debugger:post", (method, params) => this.post(method, params));
78
+ global.kuzzle.onAsk("core:debugger:isEnabled", () => this.debuggerStatus);
79
+ global.kuzzle.onAsk("core:debugger:removeListener", (event, connectionId) => this.removeListener(event, connectionId));
80
+ global.kuzzle.onAsk("core:debugger:addListener", (event, connectionId) => this.addListener(event, connectionId));
81
+ }
82
+ /**
83
+ * Connect the debugger
84
+ */
85
+ async enable() {
86
+ if (this.debuggerStatus) {
87
+ return;
88
+ }
89
+ this.inspector.connect();
90
+ this.debuggerStatus = true;
91
+ await global.kuzzle.ask("cluster:node:preventEviction", true);
92
+ }
93
+ /**
94
+ * Disconnect the debugger and clears all the events listeners
95
+ */
96
+ async disable() {
97
+ if (!this.debuggerStatus) {
98
+ return;
99
+ }
100
+ this.inspector.disconnect();
101
+ this.debuggerStatus = false;
102
+ await global.kuzzle.ask("cluster:node:preventEviction", false);
103
+ this.events.clear();
104
+ }
105
+ /**
106
+ * Trigger action from debugger directly following the Chrome Debug Protocol
107
+ * See: https://chromedevtools.github.io/devtools-protocol/v8/
108
+ */
109
+ async post(method, params = {}) {
110
+ if (!this.debuggerStatus) {
111
+ throw kerror.get("core", "debugger", "not_enabled");
112
+ }
113
+ if (!(0, get_1.default)(global.kuzzle.config, "security.debug.native_debug_protocol")) {
114
+ throw kerror.get("core", "debugger", "native_debug_protocol_usage_denied");
115
+ }
116
+ // Always disable report progress because this params causes a segfault.
117
+ // The reason this happens is because the inspector is running inside the same thread
118
+ // as the Kuzzle Process and reportProgress forces the inspector to send events
119
+ // to the main thread, while it is being inspected by the HeapProfiler, which causes javascript code
120
+ // to be executed as the HeapProfiler is running, which causes a segfault.
121
+ // See: https://github.com/nodejs/node/issues/44634
122
+ params.reportProgress = false;
123
+ return this.inspectorPost(method, params);
124
+ }
125
+ /**
126
+ * Make the websocket connection listen and receive events from Chrome Debug Protocol
127
+ * See events from: https://chromedevtools.github.io/devtools-protocol/v8/
128
+ */
129
+ async addListener(event, connectionId) {
130
+ if (!this.debuggerStatus) {
131
+ throw kerror.get("core", "debugger", "not_enabled");
132
+ }
133
+ let listeners = this.events.get(event);
134
+ if (!listeners) {
135
+ listeners = new Set();
136
+ this.events.set(event, listeners);
137
+ }
138
+ listeners.add(connectionId);
139
+ }
140
+ /**
141
+ * Remove the websocket connection from the events" listeners
142
+ */
143
+ async removeListener(event, connectionId) {
144
+ if (!this.debuggerStatus) {
145
+ throw kerror.get("core", "debugger", "not_enabled");
146
+ }
147
+ const listeners = this.events.get(event);
148
+ if (listeners) {
149
+ listeners.delete(connectionId);
150
+ }
151
+ }
152
+ /**
153
+ * Execute a method using the Chrome Debug Protocol
154
+ * @param method Chrome Debug Protocol method to execute
155
+ * @param params
156
+ * @returns
157
+ */
158
+ async inspectorPost(method, params) {
159
+ if (!this.debuggerStatus) {
160
+ throw kerror.get("core", "debugger", "not_enabled");
161
+ }
162
+ let resolve;
163
+ const promise = new Promise((res) => {
164
+ resolve = res;
165
+ });
166
+ this.inspector.post(method, params, (err, res) => {
167
+ if (err) {
168
+ resolve({
169
+ error: JSON.stringify(Object.getOwnPropertyDescriptors(err)),
170
+ });
171
+ }
172
+ else {
173
+ resolve(res);
174
+ }
175
+ });
176
+ return promise;
177
+ }
178
+ /**
179
+ * Sends a direct notification to a websocket connection without having to listen to a specific room
180
+ */
181
+ async notifyConnection(connectionId, event, payload) {
182
+ global.kuzzle.entryPoint._notify({
183
+ channels: [event],
184
+ connectionId,
185
+ payload,
186
+ });
187
+ }
188
+ async notifyGlobalListeners(event, payload) {
189
+ const listeners = this.events.get("*");
190
+ if (!listeners) {
191
+ return;
192
+ }
193
+ const promises = [];
194
+ for (const connectionId of listeners) {
195
+ promises.push(this.notifyConnection(connectionId, DEBUGGER_EVENT, {
196
+ event,
197
+ result: payload,
198
+ }));
199
+ }
200
+ // No need to catch, notify is already try-catched
201
+ await Promise.all(promises);
202
+ }
203
+ }
204
+ exports.KuzzleDebugger = KuzzleDebugger;
205
+ //# sourceMappingURL=kuzzleDebugger.js.map
@@ -228,7 +228,7 @@ class DumpGenerator {
228
228
  while (dumps.length >= config.history.reports) {
229
229
  const dir = dumps.shift().path;
230
230
 
231
- fs.removeSync(dir);
231
+ fs.rmdirSync(dir, { recursive: true });
232
232
  }
233
233
 
234
234
  for (let i = 0; i < dumps.length - config.history.coredump; i++) {
@@ -80,6 +80,7 @@ const package_json_1 = require("../../package.json");
80
80
  const name_generator_1 = require("../util/name-generator");
81
81
  const openapi_1 = require("../api/openapi");
82
82
  const crypto_1 = require("../util/crypto");
83
+ const kuzzleDebugger_1 = require("../core/debug/kuzzleDebugger");
83
84
  exports.BACKEND_IMPORT_KEY = "backend:init:import";
84
85
  let _kuzzle = null;
85
86
  Reflect.defineProperty(global, "kuzzle", {
@@ -119,6 +120,7 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
119
120
  this.dumpGenerator = new dumpGenerator_1.default();
120
121
  this.vault = null;
121
122
  this.asyncStore = new asyncStore_1.default();
123
+ this.debugger = new kuzzleDebugger_1.KuzzleDebugger();
122
124
  this.version = package_json_1.version;
123
125
  this.importTypes = {
124
126
  fixtures: this.importFixtures.bind(this),
@@ -146,6 +148,7 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
146
148
  regExpEngine: this.config.realtime.pcreSupport ? "js" : "re2",
147
149
  seed: this.config.internal.hash.seed,
148
150
  });
151
+ await this.debugger.init();
149
152
  await new cacheEngine_1.default().init();
150
153
  await new storageEngine_1.default().init();
151
154
  await new realtime_1.default().init();
@@ -681,7 +681,7 @@ class ElasticSearch extends Service {
681
681
  index,
682
682
  collection,
683
683
  content,
684
- { id, refresh, userId = null } = {}
684
+ { id, refresh, userId = null, injectKuzzleMeta = true } = {}
685
685
  ) {
686
686
  assertIsObject(content);
687
687
 
@@ -697,12 +697,14 @@ class ElasticSearch extends Service {
697
697
  assertWellFormedRefresh(esRequest);
698
698
 
699
699
  // Add metadata
700
- esRequest.body._kuzzle_info = {
701
- author: getKuid(userId),
702
- createdAt: Date.now(),
703
- updatedAt: null,
704
- updater: null,
705
- };
700
+ if (injectKuzzleMeta) {
701
+ esRequest.body._kuzzle_info = {
702
+ author: getKuid(userId),
703
+ createdAt: Date.now(),
704
+ updatedAt: null,
705
+ updater: null,
706
+ };
707
+ }
706
708
 
707
709
  debug("Create document: %o", esRequest);
708
710
 
@@ -789,7 +791,7 @@ class ElasticSearch extends Service {
789
791
  collection,
790
792
  id,
791
793
  content,
792
- { refresh, userId = null, retryOnConflict } = {}
794
+ { refresh, userId = null, retryOnConflict, injectKuzzleMeta = true } = {}
793
795
  ) {
794
796
  const esRequest = {
795
797
  _source: true,
@@ -804,11 +806,13 @@ class ElasticSearch extends Service {
804
806
  assertNoRouting(esRequest);
805
807
  assertWellFormedRefresh(esRequest);
806
808
 
807
- // Add metadata
808
- esRequest.body.doc._kuzzle_info = {
809
- updatedAt: Date.now(),
810
- updater: getKuid(userId),
811
- };
809
+ if (injectKuzzleMeta) {
810
+ // Add metadata
811
+ esRequest.body.doc._kuzzle_info = {
812
+ updatedAt: Date.now(),
813
+ updater: getKuid(userId),
814
+ };
815
+ }
812
816
 
813
817
  debug("Update document: %o", esRequest);
814
818
 
@@ -841,7 +845,13 @@ class ElasticSearch extends Service {
841
845
  collection,
842
846
  id,
843
847
  content,
844
- { defaultValues = {}, refresh, userId = null, retryOnConflict } = {}
848
+ {
849
+ defaultValues = {},
850
+ refresh,
851
+ userId = null,
852
+ retryOnConflict,
853
+ injectKuzzleMeta = true,
854
+ } = {}
845
855
  ) {
846
856
  const esRequest = {
847
857
  _source: true,
@@ -863,14 +873,16 @@ class ElasticSearch extends Service {
863
873
  const user = getKuid(userId);
864
874
  const now = Date.now();
865
875
 
866
- esRequest.body.doc._kuzzle_info = {
867
- updatedAt: now,
868
- updater: user,
869
- };
870
- esRequest.body.upsert._kuzzle_info = {
871
- author: user,
872
- createdAt: now,
873
- };
876
+ if (injectKuzzleMeta) {
877
+ esRequest.body.doc._kuzzle_info = {
878
+ updatedAt: now,
879
+ updater: user,
880
+ };
881
+ esRequest.body.upsert._kuzzle_info = {
882
+ author: user,
883
+ createdAt: now,
884
+ };
885
+ }
874
886
 
875
887
  debug("Upsert document: %o", esRequest);
876
888
 
@@ -904,7 +916,7 @@ class ElasticSearch extends Service {
904
916
  collection,
905
917
  id,
906
918
  content,
907
- { refresh, userId = null } = {}
919
+ { refresh, userId = null, injectKuzzleMeta = true } = {}
908
920
  ) {
909
921
  const alias = this._getAlias(index, collection);
910
922
  const esRequest = {
@@ -917,13 +929,15 @@ class ElasticSearch extends Service {
917
929
  assertNoRouting(esRequest);
918
930
  assertWellFormedRefresh(esRequest);
919
931
 
920
- // Add metadata
921
- esRequest.body._kuzzle_info = {
922
- author: getKuid(userId),
923
- createdAt: Date.now(),
924
- updatedAt: Date.now(),
925
- updater: getKuid(userId),
926
- };
932
+ if (injectKuzzleMeta) {
933
+ // Add metadata
934
+ esRequest.body._kuzzle_info = {
935
+ author: getKuid(userId),
936
+ createdAt: Date.now(),
937
+ updatedAt: Date.now(),
938
+ updater: getKuid(userId),
939
+ };
940
+ }
927
941
 
928
942
  try {
929
943
  const { body: exists } = await this._client.exists({ id, index: alias });
@@ -1,4 +1,4 @@
1
- import { KuzzleRequest, KDocument, JSONObject } from "../../../";
1
+ import { KuzzleRequest, KDocument, JSONObject, PipeEventHandler } from "../../../";
2
2
  /**
3
3
  * Events with documents only having the `_id`
4
4
  */
@@ -23,4 +23,24 @@ export type EventGenericDocumentAfterWrite<KDocumentContent = JSONObject> = Even
23
23
  export type EventGenericDocumentBeforeUpdate<KDocumentContent = JSONObject> = EventGenericDocument<"beforeUpdate", KDocumentContent>;
24
24
  export type EventGenericDocumentAfterUpdate<KDocumentContent = JSONObject> = EventGenericDocument<"afterUpdate", KDocumentContent>;
25
25
  export type EventGenericDocumentAfterGet<KDocumentContent = JSONObject> = EventGenericDocument<"afterGet", KDocumentContent>;
26
+ export type EventGenericDocumentInjectMetadata = {
27
+ name: `generic:document:injectMetadata`;
28
+ args: [
29
+ {
30
+ /**
31
+ * Kuzzle Request that triggered the event
32
+ */
33
+ request: KuzzleRequest;
34
+ /**
35
+ * Metadata of the document
36
+ */
37
+ metadata: JSONObject;
38
+ /**
39
+ * Default metadata of the document.
40
+ * Only used when calling document:upsert.
41
+ */
42
+ defaultMetadata?: JSONObject;
43
+ }
44
+ ];
45
+ } & PipeEventHandler;
26
46
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kuzzle",
3
3
  "author": "The Kuzzle Team <support@kuzzle.io>",
4
- "version": "2.20.3",
4
+ "version": "2.21.0",
5
5
  "description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.",
6
6
  "bin": "bin/start-kuzzle-server",
7
7
  "scripts": {
@@ -1,24 +0,0 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import EventEmitter from "events";
4
- import Inspector from "inspector";
5
- export type DebugModuleOptions = {
6
- methods?: string[];
7
- events?: string[];
8
- };
9
- export declare abstract class DebugModule extends EventEmitter {
10
- name: string;
11
- methods: string[];
12
- events: string[];
13
- /**
14
- * Called when the module is loaded, after the debugger has been enabled
15
- */
16
- abstract init(inspector: Inspector.Session): Promise<void>;
17
- /**
18
- * Called when the module should be cleaned up.
19
- * - After the Debug Controller has been disabled
20
- * - Before the debugger is disconnected
21
- */
22
- abstract cleanup(): Promise<void>;
23
- constructor(name: string, options?: DebugModuleOptions);
24
- }
@@ -1,39 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DebugModule = void 0;
7
- const events_1 = __importDefault(require("events"));
8
- class DebugModule extends events_1.default {
9
- constructor(name, options = {}) {
10
- super();
11
- this.name = name;
12
- this.methods = options.methods || [];
13
- this.events = options.events || [];
14
- if (!this.name || this.name.length === 0) {
15
- throw new Error("DebugModule should have a name");
16
- }
17
- if (this.name.charAt(0) !== this.name.charAt(0).toUpperCase()) {
18
- throw new Error(`Debug Module name "${name}" should start with an uppercase letter`);
19
- }
20
- for (const event of this.events) {
21
- if (event.length === 0) {
22
- throw new Error(`Event name should not be empty for "${name}"`);
23
- }
24
- if (event.charAt(0) !== event.charAt(0).toLowerCase()) {
25
- throw new Error(`Event name "${event}" should start with a lowercase letter for module "${name}"`);
26
- }
27
- }
28
- for (const method of this.methods) {
29
- if (method.length === 0) {
30
- throw new Error(`Method name should not be empty for Debug Module "${name}"`);
31
- }
32
- if (method.charAt(0) !== method.charAt(0).toLowerCase()) {
33
- throw new Error(`Method name "${method}" should start with a lowercase letter for module "${name}"`);
34
- }
35
- }
36
- }
37
- }
38
- exports.DebugModule = DebugModule;
39
- //# sourceMappingURL=DebugModule.js.map