@joystick.js/node-canary 0.0.0-canary.30 → 0.0.0-canary.301

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.
Files changed (90) hide show
  1. package/_package.json +2 -2
  2. package/dist/action/class.js +21 -0
  3. package/dist/api/get.js +15 -13
  4. package/dist/api/set.js +15 -13
  5. package/dist/app/accounts/createMetadataTableColumns.js +12 -0
  6. package/dist/app/accounts/deleteUser.js +7 -0
  7. package/dist/app/accounts/generateSession.js +2 -4
  8. package/dist/app/accounts/getBrowserSafeUser.js +5 -2
  9. package/dist/app/accounts/hasLoginTokenExpired.js +1 -2
  10. package/dist/app/accounts/index.js +2 -0
  11. package/dist/app/accounts/login.js +6 -0
  12. package/dist/app/accounts/recoverPassword.js +3 -0
  13. package/dist/app/accounts/resetPassword.js +6 -0
  14. package/dist/app/accounts/signup.js +50 -7
  15. package/dist/app/accounts/verifyEmail.js +3 -0
  16. package/dist/app/cluster.js +26 -0
  17. package/dist/app/databases/mongodb/buildConnectionString.js +1 -1
  18. package/dist/app/databases/mongodb/createAccountsIndexes.js +18 -0
  19. package/dist/app/databases/mongodb/createSessionsIndexes.js +10 -0
  20. package/dist/app/databases/mongodb/index.js +1 -1
  21. package/dist/app/databases/mongodb/queries/accounts.js +8 -1
  22. package/dist/app/databases/mongodb/queries/queues.js +34 -25
  23. package/dist/app/databases/mongodb/queries/sessions.js +26 -0
  24. package/dist/app/databases/postgresql/createSessionsIndexes.js +10 -0
  25. package/dist/app/databases/postgresql/createSessionsTables.js +14 -0
  26. package/dist/app/databases/postgresql/handleCleanupQueues.js +35 -0
  27. package/dist/app/databases/postgresql/index.js +9 -2
  28. package/dist/app/databases/postgresql/queries/accounts.js +5 -2
  29. package/dist/app/databases/postgresql/queries/queues.js +69 -36
  30. package/dist/app/databases/postgresql/queries/sessions.js +43 -0
  31. package/dist/app/databases/queryMap.js +6 -2
  32. package/dist/app/databases/stringToSnakeCase.js +6 -0
  33. package/dist/app/getBrowserSafeRequest.js +3 -2
  34. package/dist/app/index.js +214 -73
  35. package/dist/app/initExpress.js +1 -1
  36. package/dist/app/middleware/csp.js +2 -2
  37. package/dist/app/middleware/getTranslations.js +64 -0
  38. package/dist/app/middleware/get_insecure_landing_page_html.js +71 -0
  39. package/dist/app/middleware/hmr/client.js +13 -9
  40. package/dist/app/middleware/index.js +6 -5
  41. package/dist/app/middleware/insecure.js +3 -4
  42. package/dist/app/middleware/render.js +154 -0
  43. package/dist/app/middleware/session.js +12 -11
  44. package/dist/app/queues/index.js +74 -27
  45. package/dist/app/registerGetters.js +5 -6
  46. package/dist/app/registerSetters.js +5 -6
  47. package/dist/app/runGetter.js +17 -5
  48. package/dist/app/runSessionQuery.js +15 -0
  49. package/dist/app/runSetter.js +17 -5
  50. package/dist/app/sanitizeAPIResponse.js +1 -1
  51. package/dist/app/validateSession.js +8 -3
  52. package/dist/app/validateUploaderOptions.js +3 -3
  53. package/dist/app/validateUploads.js +12 -1
  54. package/dist/email/send.js +7 -1
  55. package/dist/email/templates/reset-password.js +0 -1
  56. package/dist/fixture/index.js +40 -0
  57. package/dist/index.js +15 -0
  58. package/dist/lib/escapeKeyValuePair.js +13 -0
  59. package/dist/lib/formatAPIError.js +0 -1
  60. package/dist/lib/getBuildPath.js +1 -1
  61. package/dist/lib/getSSLCertificates.js +3 -3
  62. package/dist/lib/importFile.js +7 -0
  63. package/dist/lib/isValidJSONString.js +1 -1
  64. package/dist/lib/log.js +0 -3
  65. package/dist/lib/objectToSQLKeysString.js +1 -1
  66. package/dist/lib/objectToSQLValuesString.js +1 -1
  67. package/dist/lib/serializeQueryParameters.js +1 -1
  68. package/dist/lib/timestamps.js +47 -0
  69. package/dist/lib/wait.js +8 -0
  70. package/dist/push/logs/index.js +6 -1
  71. package/dist/settings/load.js +3 -5
  72. package/dist/ssr/compileCSS.js +4 -4
  73. package/dist/ssr/findComponentInTree.js +1 -1
  74. package/dist/ssr/getAPIForDataFunctions.js +35 -0
  75. package/dist/ssr/getDataFromComponent.js +15 -0
  76. package/dist/ssr/index.js +19 -45
  77. package/dist/ssr/replaceWhenTags.js +2 -3
  78. package/dist/ssr/setHeadTagsInHTML.js +3 -3
  79. package/dist/test/index.js +9 -0
  80. package/dist/test/trackFunctionCall.js +17 -0
  81. package/dist/validation/inputWithSchema/index.js +3 -3
  82. package/dist/validation/schema/index.js +5 -5
  83. package/dist/websockets/index.js +4 -0
  84. package/getSanitizedContext.js +43 -0
  85. package/package.json +2 -1
  86. package/dist/app/accounts/roles/index.test.js +0 -123
  87. package/dist/app/index.test.js +0 -575
  88. package/dist/app/middleware/sanitizeRequestParameters.js +0 -21
  89. package/dist/email/send.test.js +0 -37
  90. package/dist/validation/index.test.js +0 -463
package/dist/app/index.js CHANGED
@@ -6,6 +6,8 @@ import queryString from "query-string";
6
6
  import multer from "multer";
7
7
  import cron from "node-cron";
8
8
  import { execSync } from "child_process";
9
+ import dayjs from "dayjs";
10
+ import cluster from "./cluster.js";
9
11
  import initExpress from "./initExpress.js";
10
12
  import handleProcessErrors from "./handleProcessErrors";
11
13
  import registerGetters from "./registerGetters.js";
@@ -25,49 +27,58 @@ import runUploader from "./runUploader";
25
27
  import generateId from "../lib/generateId.js";
26
28
  import getOutput from "./getOutput.js";
27
29
  import defaultUserOutputFields from "./accounts/defaultUserOutputFields.js";
30
+ import createMongoDBAccountsIndexes from "./databases/mongodb/createAccountsIndexes";
31
+ import createMongoDBSessionsIndexes from "./databases/mongodb/createSessionsIndexes";
28
32
  import createPostgreSQLAccountsTables from "./databases/postgresql/createAccountsTables";
29
33
  import createPostgreSQLAccountsIndexes from "./databases/postgresql/createAccountsIndexes";
34
+ import createPostgreSQLSessionsTables from "./databases/postgresql/createSessionsTables";
35
+ import createPostgreSQLSessionsIndexes from "./databases/postgresql/createSessionsIndexes";
30
36
  import loadSettings from "../settings/load.js";
31
37
  import Queue from "./queues/index.js";
32
38
  import readDirectory from "../lib/readDirectory.js";
33
39
  import getBuildPath from "../lib/getBuildPath.js";
34
40
  import generateMachineId from "../lib/generateMachineId.js";
41
+ import importFile from "../lib/importFile.js";
35
42
  import emitWebsocketEvent from "../websockets/emitWebsocketEvent.js";
36
43
  import getTargetDatabaseConnection from "./databases/getTargetDatabaseConnection.js";
44
+ import getAPIForDataFunctions from "../ssr/getAPIForDataFunctions.js";
45
+ import getBrowserSafeRequest from "./getBrowserSafeRequest.js";
46
+ import getDataFromComponent from "../ssr/getDataFromComponent.js";
47
+ import getTranslations from "./middleware/getTranslations.js";
48
+ import runUserQuery from "./accounts/runUserQuery.js";
49
+ import wait from "../lib/wait.js";
50
+ import trackFunctionCall from "../test/trackFunctionCall.js";
51
+ import getBrowserSafeUser from "./accounts/getBrowserSafeUser.js";
37
52
  process.setMaxListeners(0);
38
53
  class App {
39
54
  constructor(options = {}) {
40
55
  this.setMachineId();
41
56
  this.setJoystickProcessId();
42
57
  handleProcessErrors(options?.events);
43
- const HMRSessions = JSON.parse(process.env.HMR_SESSIONS || "{}");
44
- this.sessions = new Map(HMRSessions ? Object.entries(HMRSessions) : []);
45
58
  this.databases = [];
46
59
  this.express = {};
47
60
  this.options = options || {};
61
+ process.joystick = {
62
+ _app: {
63
+ options
64
+ }
65
+ };
48
66
  }
49
67
  async start(options = {}) {
50
68
  await this.invalidateCache();
51
69
  this.databases = await this.loadDatabases();
52
70
  this.express = initExpress(this.onStartApp, options, this);
53
71
  this.initWebsockets(options?.websockets || {});
54
- this.initDevelopmentRoutes();
55
- this.initAccounts();
72
+ this.initAccounts(options?.accounts);
73
+ this.initTests();
56
74
  this.initDeploy();
57
75
  this.initAPI(options?.api);
58
- this.initRoutes(options?.routes);
59
76
  this.initUploaders(options?.uploaders);
77
+ this.initIndexes(options?.indexes);
60
78
  this.initFixtures(options?.fixtures);
61
79
  this.initQueues(options?.queues);
62
80
  this.initCronJobs(options?.cronJobs);
63
- if (process.env.NODE_ENV === "development") {
64
- process.on("message", (message) => {
65
- const parsedMessage = typeof message === "string" ? JSON.parse(message) : message;
66
- if (parsedMessage?.type === " RESTART_SERVER") {
67
- this.express?.server?.close();
68
- }
69
- });
70
- }
81
+ this.initRoutes(options?.routes);
71
82
  }
72
83
  async invalidateCache() {
73
84
  const uiFiles = fs.existsSync(`${getBuildPath()}ui`) ? await readDirectory(`${getBuildPath()}ui`) : [];
@@ -85,9 +96,21 @@ class App {
85
96
  const hasQueuesDatabase = settings?.config?.databases?.some((database = {}) => {
86
97
  return !!database?.queues;
87
98
  });
99
+ const hasSessionsDatabase = settings?.config?.databases?.some((database = {}) => {
100
+ return !!database?.sessions;
101
+ });
102
+ const hasMongoDBUsersDatabase = settings?.config?.databases?.some((database = {}) => {
103
+ return database?.provider === "mongodb" && database?.users;
104
+ });
105
+ const hasMongoDBSessionsDatabase = settings?.config?.databases?.some((database = {}) => {
106
+ return database?.provider === "mongodb" && database?.sessions;
107
+ });
88
108
  const hasPostgreSQLUsersDatabase = settings?.config?.databases?.some((database = {}) => {
89
109
  return database?.provider === "postgresql" && database?.users;
90
110
  });
111
+ const hasPostgreSQLSessionsDatabase = settings?.config?.databases?.some((database = {}) => {
112
+ return database?.provider === "postgresql" && database?.sessions;
113
+ });
91
114
  const databases = settings?.config?.databases?.map((database) => {
92
115
  return {
93
116
  provider: database?.provider,
@@ -131,19 +154,40 @@ class App {
131
154
  if (hasQueuesDatabase) {
132
155
  process.databases._queues = getTargetDatabaseConnection("queues")?.connection;
133
156
  }
157
+ if (hasSessionsDatabase) {
158
+ process.databases._sessions = getTargetDatabaseConnection("sessions")?.connection;
159
+ }
160
+ if (hasMongoDBUsersDatabase) {
161
+ await createMongoDBAccountsIndexes();
162
+ }
163
+ if (hasMongoDBSessionsDatabase) {
164
+ await createMongoDBSessionsIndexes();
165
+ }
134
166
  if (hasPostgreSQLUsersDatabase) {
135
167
  await createPostgreSQLAccountsTables();
136
168
  await createPostgreSQLAccountsIndexes();
137
169
  }
170
+ if (hasPostgreSQLSessionsDatabase) {
171
+ await createPostgreSQLSessionsTables();
172
+ await createPostgreSQLSessionsIndexes();
173
+ }
138
174
  return process.databases;
139
175
  }
140
- onStartApp(express = {}) {
176
+ onStartApp(express = {}, joystick_app_instance = {}) {
141
177
  process.on("message", (message) => {
142
178
  if (typeof message === "string") {
143
- process.BUILD_ERROR = JSON.parse(message);
179
+ const parsed_message = JSON.parse(message);
180
+ if (parsed_message?.type === "BUILD_ERROR") {
181
+ process.BUILD_ERROR = JSON.parse(message);
182
+ }
183
+ if (parsed_message?.type === "RESTART_SERVER") {
184
+ console.log("HANDLE RESTART");
185
+ }
144
186
  }
145
187
  });
146
- console.log(`App running at: http://localhost:${express.port}`);
188
+ if (process.env.NODE_ENV !== "test") {
189
+ console.log(`App running at: http://localhost:${express.port}`);
190
+ }
147
191
  }
148
192
  setMachineId() {
149
193
  generateMachineId();
@@ -156,10 +200,78 @@ class App {
156
200
  fs.writeFileSync("./.joystick/PROCESS_ID", `${generateId(32)}`);
157
201
  }
158
202
  }
203
+ initTests() {
204
+ if (process.env.NODE_ENV === "test") {
205
+ this.express.app.get("/api/_test/bootstrap", async (req, res) => {
206
+ const buildPath = `${process.cwd()}/.joystick/build`;
207
+ const Component = req?.query?.pathToComponent ? await importFile(`${buildPath}/${req?.query?.pathToComponent}`) : null;
208
+ if (Component) {
209
+ const componentInstance = Component();
210
+ const apiForDataFunctions = await getAPIForDataFunctions(req, this?.options?.api);
211
+ const browserSafeRequest = getBrowserSafeRequest(req);
212
+ const browserSafeUser = getBrowserSafeUser(req?.context?.user);
213
+ const data = await getDataFromComponent(componentInstance, apiForDataFunctions, browserSafeUser, browserSafeRequest);
214
+ const translations = await getTranslations({ build: buildPath, page: req?.query?.pathToComponent }, req);
215
+ const settings = loadSettings();
216
+ return res.status(200).send({
217
+ data: {
218
+ [data?.componentId]: data?.data
219
+ },
220
+ req: browserSafeRequest,
221
+ settings,
222
+ translations
223
+ });
224
+ }
225
+ res.status(200).send({ data: {}, translations: {} });
226
+ });
227
+ this.express.app.get("/api/_test/process", async (req, res) => {
228
+ res.status(200).send({
229
+ test: process?.test || {}
230
+ });
231
+ });
232
+ this.express.app.post("/api/_test/accounts/signup", async (req, res) => {
233
+ const existingUser = await runUserQuery("user", { emailAddress: req?.body?.emailAddress });
234
+ if (existingUser) {
235
+ await runUserQuery("deleteUser", { userId: existingUser?._id || existingUser?.user_id });
236
+ }
237
+ const signup = await accounts.signup({
238
+ emailAddress: req?.body?.emailAddress,
239
+ password: req?.body?.password,
240
+ metadata: req?.body?.metadata,
241
+ output: req?.body?.output || defaultUserOutputFields
242
+ });
243
+ res.status(200).send(JSON.stringify({
244
+ ...signup?.user || {},
245
+ joystickLoginToken: signup?.token,
246
+ joystickLoginTokenExpiresAt: signup?.tokenExpiresAt
247
+ }));
248
+ });
249
+ this.express.app.delete("/api/_test/accounts", async (req, res) => {
250
+ await runUserQuery("deleteUser", { userId: req?.body?.userId });
251
+ res.status(200).send({ data: {} });
252
+ });
253
+ this.express.app.post("/api/_test/queues", async (req, res) => {
254
+ const queue = process?.queues[req?.body?.queue];
255
+ const job = this?.options?.queues[req?.body?.queue]?.jobs[req?.body?.job];
256
+ if (!queue) {
257
+ return res.status(404).send({ status: 404, error: `Queue ${req?.body?.queue} not found.` });
258
+ }
259
+ if (!job) {
260
+ return res.status(400).send({ status: 400, error: `Couldn't find a job called ${req?.body?.job} for the ${req?.body?.queue} queue.` });
261
+ }
262
+ await queue.handleNextJob({
263
+ _id: "joystick_test",
264
+ job: req?.body?.job,
265
+ payload: req?.body?.payload
266
+ });
267
+ res.status(200).send({ data: {} });
268
+ });
269
+ }
270
+ }
159
271
  initDeploy() {
160
272
  if (process.env.NODE_ENV === "production" && process.env.IS_PUSH_DEPLOYED) {
161
273
  this.express.app.get("/api/_push/pre-version", async (req, res) => {
162
- const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
274
+ const instanceToken = fs.readFileSync("/root/push/instance_token.txt", "utf-8");
163
275
  if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
164
276
  if (this.options?.events?.onBeforeDeployment && typeof this.options?.events?.onBeforeDeployment === "function") {
165
277
  await this.options.events.onBeforeDeployment(req?.query?.instance || "", req?.query?.version);
@@ -170,28 +282,12 @@ class App {
170
282
  return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
171
283
  });
172
284
  this.express.app.get("/api/_push/health", async (req, res) => {
173
- const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
285
+ const instanceToken = fs.readFileSync("/root/push/instance_token.txt", "utf-8");
174
286
  if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
175
287
  return res.status(200).send("ok");
176
288
  }
177
289
  return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
178
290
  });
179
- this.express.app.get("/api/_push/logs", async (req, res) => {
180
- const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
181
- if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
182
- const logs = execSync(`export NODE_ENV=production && instance logs${req?.query?.before ? ` --before ${req?.query?.before}` : ""}${req?.query?.after ? ` --after ${req?.query?.after}` : ""}`);
183
- return res.status(200).send(logs);
184
- }
185
- return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
186
- });
187
- this.express.app.get("/api/_push/metrics", async (req, res) => {
188
- const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
189
- if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
190
- const metrics = execSync(`export NODE_ENV=production && instance metrics`);
191
- return res.status(200).send(metrics);
192
- }
193
- return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
194
- });
195
291
  }
196
292
  }
197
293
  initAPI(api = {}) {
@@ -199,15 +295,15 @@ class App {
199
295
  const setters = api?.setters;
200
296
  const options = api?.options;
201
297
  const context = api?.context;
202
- if (getters && isObject(getters) && Object.keys(getters).length > 0) {
203
- registerGetters(this.express, Object.entries(getters), context, options, this);
298
+ if (getters && isObject(getters) && Object.keys(getters || {}).length > 0) {
299
+ registerGetters(this.express, Object.entries(getters || {}), context, options, this);
204
300
  }
205
- if (setters && isObject(setters) && Object.keys(setters).length > 0) {
206
- registerSetters(this.express, Object.entries(setters), context, options, this);
301
+ if (setters && isObject(setters) && Object.keys(setters || {}).length > 0) {
302
+ registerSetters(this.express, Object.entries(setters || {}), context, options, this);
207
303
  }
208
304
  }
209
305
  initRoutes(routes = {}) {
210
- Object.entries(routes).forEach(([path, callback]) => {
306
+ Object.entries(routes || {}).forEach(([path, callback]) => {
211
307
  const isObjectBasedRoute = path && callback && typeof callback === "object";
212
308
  const isFunctionBasedRoute = path && callback && typeof callback === "function";
213
309
  const method = callback?.method?.toLowerCase();
@@ -331,20 +427,29 @@ class App {
331
427
  path: "/api/_websockets/uploaders"
332
428
  })
333
429
  },
334
- ...Object.entries(userWebsockets).reduce((definitions = {}, [userWebsocketName, userWebsocketDefinition]) => {
430
+ ...Object.entries(userWebsockets || {}).reduce((definitions = {}, [userWebsocketName, userWebsocketDefinition]) => {
335
431
  definitions[userWebsocketName] = {
336
432
  server: new WebSocket.WebSocketServer({
337
433
  noServer: true,
338
434
  path: `/api/_websockets/${userWebsocketName}`
339
435
  }),
340
- onOpen: userWebsocketDefinition?.onOpen || null,
341
- onMessage: userWebsocketDefinition?.onMessage || null,
342
- onClose: userWebsocketDefinition?.onClose || null
436
+ onOpen: (...args) => {
437
+ trackFunctionCall(`node.websockets.${userWebsocketName}.onOpen`, args);
438
+ return userWebsocketDefinition?.onOpen ? userWebsocketDefinition?.onOpen(...args) : null;
439
+ },
440
+ onMessage: (...args) => {
441
+ trackFunctionCall(`node.websockets.${userWebsocketName}.onMessage`, args);
442
+ return userWebsocketDefinition?.onMessage ? userWebsocketDefinition?.onMessage(...args) : null;
443
+ },
444
+ onClose: (...args) => {
445
+ trackFunctionCall(`node.websockets.${userWebsocketName}.onClose`, args);
446
+ return userWebsocketDefinition?.onClose ? userWebsocketDefinition.onClose(...args) : null;
447
+ }
343
448
  };
344
449
  return definitions;
345
450
  }, {})
346
451
  };
347
- Object.entries(websocketServers).forEach(([websocketName, websocketDefinition]) => {
452
+ Object.entries(websocketServers || {}).forEach(([websocketName, websocketDefinition]) => {
348
453
  websocketDefinition.server.on("connection", function connection(websocketConnection, connectionRequest) {
349
454
  try {
350
455
  const [_path, params] = connectionRequest?.url?.split("?");
@@ -400,18 +505,7 @@ class App {
400
505
  }
401
506
  });
402
507
  }
403
- initDevelopmentRoutes() {
404
- if (process.env.NODE_ENV === "development") {
405
- this.express.app.get("/api/_joystick/sessions", async (req, res) => {
406
- const sessions = Array.from(this.sessions.entries())?.reduce((acc = {}, [key, value]) => {
407
- acc[key] = value;
408
- return acc;
409
- }, {});
410
- res.status(200).send(JSON.stringify(sessions));
411
- });
412
- }
413
- }
414
- initAccounts() {
508
+ initAccounts(options = {}) {
415
509
  this.express.app.get("/api/_accounts/authenticated", async (req, res) => {
416
510
  const loginTokenHasExpired = await hasLoginTokenExpired(res, req?.cookies?.joystickLoginToken, req?.cookies?.joystickLoginTokenExpiresAt);
417
511
  const status = !loginTokenHasExpired ? 200 : 401;
@@ -431,11 +525,20 @@ class App {
431
525
  metadata: req?.body?.metadata,
432
526
  output: req?.body?.output || defaultUserOutputFields
433
527
  });
434
- accounts._setAuthenticationCookie(res, {
435
- token: signup?.token,
436
- tokenExpiresAt: signup?.tokenExpiresAt
437
- });
438
- res.status(200).send(JSON.stringify(signup?.user || {}));
528
+ if (!process.env.NODE_ENV !== "test") {
529
+ accounts._setAuthenticationCookie(res, {
530
+ token: signup?.token,
531
+ tokenExpiresAt: signup?.tokenExpiresAt
532
+ });
533
+ }
534
+ const response = {
535
+ ...signup?.user || {}
536
+ };
537
+ if (process.env.NODE_ENV === "test") {
538
+ response.joystickToken = signup?.token;
539
+ response.joystickLoginTokenExpiresAt = signup?.tokenExpiresAt;
540
+ }
541
+ res.status(200).send(JSON.stringify(response));
439
542
  } catch (exception) {
440
543
  console.log(exception);
441
544
  return res.status(500).send(JSON.stringify({
@@ -451,11 +554,20 @@ class App {
451
554
  password: req?.body?.password,
452
555
  output: req?.body?.output || defaultUserOutputFields
453
556
  });
454
- accounts._setAuthenticationCookie(res, {
455
- token: login?.token,
456
- tokenExpiresAt: login?.tokenExpiresAt
457
- });
458
- res.status(200).send(JSON.stringify(login?.user || {}));
557
+ if (!process.env.NODE_ENV !== "test") {
558
+ accounts._setAuthenticationCookie(res, {
559
+ token: login?.token,
560
+ tokenExpiresAt: login?.tokenExpiresAt
561
+ });
562
+ }
563
+ const response = {
564
+ ...login?.user || {}
565
+ };
566
+ if (process.env.NODE_ENV === "test") {
567
+ response.joystickToken = login?.token;
568
+ response.joystickLoginTokenExpiresAt = login?.tokenExpiresAt;
569
+ }
570
+ res.status(200).send(JSON.stringify(response));
459
571
  } catch (exception) {
460
572
  console.log(exception);
461
573
  return res.status(500).send(JSON.stringify({
@@ -465,8 +577,10 @@ class App {
465
577
  });
466
578
  this.express.app.post("/api/_accounts/logout", async (req, res) => {
467
579
  try {
468
- this.sessions.delete(req?.context?.user?._id || req?.context?.user?.user_id);
469
580
  accounts._unsetAuthenticationCookie(res);
581
+ if (typeof options?.onLogout === "function") {
582
+ options.onLogout(req?.context?.user);
583
+ }
470
584
  res.status(200).send(JSON.stringify({}));
471
585
  } catch (exception) {
472
586
  console.log(exception);
@@ -523,7 +637,7 @@ class App {
523
637
  }
524
638
  initUploaders(uploaders = {}) {
525
639
  const { app } = this.express;
526
- Object.entries(uploaders).forEach(([uploaderName, uploaderOptions]) => {
640
+ Object.entries(uploaders || {}).forEach(([uploaderName, uploaderOptions]) => {
527
641
  const errors = validateUploaderOptions(uploaderOptions);
528
642
  if (errors?.length > 0) {
529
643
  log(errors, {
@@ -561,6 +675,11 @@ class App {
561
675
  req,
562
676
  uploads: validatedUploads
563
677
  });
678
+ trackFunctionCall(`node.uploaders.${uploaderName}.onBeforeUpload`, [{
679
+ input,
680
+ req,
681
+ uploads: validatedUploads
682
+ }]);
564
683
  }
565
684
  const fileSize = parseInt(req.headers["content-length"], 10);
566
685
  const providers = uploaderOptions?.providers?.includes("local") ? uploaderOptions?.providers.length : uploaderOptions?.providers?.length + 1;
@@ -578,6 +697,11 @@ class App {
578
697
  req,
579
698
  uploads
580
699
  });
700
+ trackFunctionCall(`node.uploaders.${uploaderName}.onAfterUpload`, [{
701
+ input,
702
+ req,
703
+ uploads
704
+ }]);
581
705
  }
582
706
  res.status(200).send(JSON.stringify({
583
707
  status: 200,
@@ -604,6 +728,11 @@ class App {
604
728
  }
605
729
  });
606
730
  }
731
+ initIndexes(indexes = null) {
732
+ if (indexes && typeof indexes === "function") {
733
+ indexes();
734
+ }
735
+ }
607
736
  initFixtures(fixtures = null) {
608
737
  if (fixtures && typeof fixtures === "function") {
609
738
  fixtures();
@@ -611,7 +740,7 @@ class App {
611
740
  }
612
741
  initQueues(queues = null) {
613
742
  if (queues && typeof queues === "object" && !Array.isArray(queues)) {
614
- const queueDefinitions = Object.entries(queues);
743
+ const queueDefinitions = Object.entries(queues || {});
615
744
  for (let i = 0; i < queueDefinitions.length; i += 1) {
616
745
  const [queueName, queueOptions] = queueDefinitions[i];
617
746
  process.queues = {
@@ -623,7 +752,7 @@ class App {
623
752
  }
624
753
  initCronJobs(cronJobs = null) {
625
754
  if (cronJobs && typeof cronJobs === "object" && !Array.isArray(cronJobs)) {
626
- const cronJobDefinitions = Object.entries(cronJobs);
755
+ const cronJobDefinitions = Object.entries(cronJobs || {});
627
756
  for (let i = 0; i < cronJobDefinitions.length; i += 1) {
628
757
  const [cronJobName, cronJobOptions] = cronJobDefinitions[i];
629
758
  if (cronJobOptions?.schedule && cronJobOptions?.run && typeof cronJobOptions?.run === "function") {
@@ -641,11 +770,23 @@ class App {
641
770
  }
642
771
  }
643
772
  }
773
+ ;
774
+ const handleStartApp = async (options = {}) => {
775
+ const app = new App(options);
776
+ await app.start(options);
777
+ return app;
778
+ };
644
779
  var app_default = (options = {}) => {
645
780
  return new Promise(async (resolve) => {
646
- const app = new App(options);
647
- await app.start(options);
648
- return resolve(app.express);
781
+ if (options?.cluster) {
782
+ cluster(async () => {
783
+ const app = await handleStartApp(options);
784
+ return resolve(app.express);
785
+ });
786
+ } else {
787
+ const app = await handleStartApp(options);
788
+ return resolve(app.express);
789
+ }
649
790
  });
650
791
  };
651
792
  export {
@@ -29,7 +29,7 @@ var initExpress_default = (onInit = () => {
29
29
  server
30
30
  };
31
31
  if (onInit) {
32
- onInit(instance);
32
+ onInit(instance, appInstance);
33
33
  }
34
34
  return instance;
35
35
  } catch (exception) {
@@ -27,7 +27,7 @@ var csp_default = (req, res, next, config = null) => {
27
27
  directiveDefaults["script-src"].push("'unsafe-eval'");
28
28
  directiveDefaults["connect-src"].push("ws:");
29
29
  }
30
- const directiveNames = Object.keys(directiveDefaults);
30
+ const directiveNames = Object.keys(directiveDefaults || {});
31
31
  for (let i = 0; i < directiveNames?.length; i += 1) {
32
32
  const directive = directiveNames[i];
33
33
  directiveDefaults[directive] = [
@@ -36,7 +36,7 @@ var csp_default = (req, res, next, config = null) => {
36
36
  ...directives[directive] || []
37
37
  ];
38
38
  }
39
- const csp = Object.keys(directiveDefaults).map((source) => {
39
+ const csp = Object.keys(directiveDefaults || {}).map((source) => {
40
40
  return `${source} ${directiveDefaults[source].join(" ")}`;
41
41
  })?.join("; ");
42
42
  res.setHeader("Content-Security-Policy", csp);
@@ -0,0 +1,64 @@
1
+ import fs from "fs";
2
+ import importFile from "../../lib/importFile.js";
3
+ import { isObject } from "../../validation/lib/typeValidators.js";
4
+ import settings from "../../settings/index.js";
5
+ const getTranslationsFile = async (languageFilePath = "", paths = "") => {
6
+ const languageFile = await importFile(`${paths.build}/i18n/${languageFilePath}`);
7
+ const isValidLanguageFile = languageFile && isObject(languageFile);
8
+ if (isValidLanguageFile) {
9
+ const translationsForPage = languageFile[paths.page];
10
+ return translationsForPage ? translationsForPage : languageFile;
11
+ }
12
+ return {};
13
+ };
14
+ const getLanguagePreferenceRegexes = (userLanguage = "", browserLanguages = []) => {
15
+ let languagePreferences = [];
16
+ if (userLanguage) {
17
+ languagePreferences.push(userLanguage);
18
+ }
19
+ const filteredBrowserLanguages = browserLanguages?.filter((language) => {
20
+ return !language?.includes("*");
21
+ });
22
+ languagePreferences.push(...filteredBrowserLanguages);
23
+ languagePreferences.push(settings?.config?.i18n?.defaultLanguage);
24
+ return languagePreferences?.flatMap((language) => {
25
+ const variants = [language];
26
+ if (language?.length === 2) {
27
+ variants.push(`${language.substring(0, 2)}-`);
28
+ }
29
+ if (language?.length > 2) {
30
+ variants.push(`${language?.split("-")[0]}`);
31
+ variants.push(`${language?.split("-")[0]}-`);
32
+ }
33
+ return variants;
34
+ })?.map((languageString) => {
35
+ const lastCharacter = languageString[languageString.length - 1];
36
+ if (lastCharacter === "-") {
37
+ return new RegExp(`^${languageString}[A-Z]+.js`, "g");
38
+ }
39
+ return new RegExp(`^${languageString}.js`, "g");
40
+ });
41
+ };
42
+ const parseBrowserLanguages = (languages = "") => {
43
+ const rawLanguages = languages.split(",");
44
+ return rawLanguages?.map((rawLanguage) => rawLanguage.split(";")[0]);
45
+ };
46
+ var getTranslations_default = async (paths = {}, req = {}) => {
47
+ const languageFiles = fs.readdirSync(`${paths.build}/i18n`);
48
+ const browserLanguages = parseBrowserLanguages(req?.headers["accept-language"]);
49
+ const languagePreferences = getLanguagePreferenceRegexes(req?.context?.user?.language, browserLanguages);
50
+ let matchingFile = null;
51
+ for (let i = 0; i < languagePreferences.length; i += 1) {
52
+ const languageRegex = languagePreferences[i];
53
+ const match = languageFiles.find((languageFile) => !!languageFile.match(languageRegex));
54
+ if (match) {
55
+ matchingFile = match;
56
+ break;
57
+ }
58
+ }
59
+ const translationsFile = await getTranslationsFile(matchingFile, paths);
60
+ return translationsFile;
61
+ };
62
+ export {
63
+ getTranslations_default as default
64
+ };
@@ -0,0 +1,71 @@
1
+ var get_insecure_landing_page_html_default = (hostname = "", path = "") => {
2
+ const url = `https://${hostname}${path}`;
3
+ return `
4
+ <html>
5
+ <head>
6
+ <title>Insecure Connection</title>
7
+ <style type="text/css">
8
+ body {
9
+ font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
10
+ font-size: 16px;
11
+ line-height: 24px;
12
+ background: #fafafa;
13
+ display: flex;
14
+ margin: 0;
15
+ padding: 20px;
16
+ justify-content: center;
17
+ }
18
+
19
+ .warning {
20
+ width: 100%;
21
+ max-width: 500px;
22
+ background: #fff;
23
+ border: 1px solid #eee;
24
+ border-radius: 3px;
25
+ padding: 35px;
26
+ align-self: flex-start;
27
+ }
28
+
29
+ .warning h1 {
30
+ color: #000;
31
+ font-size: 22px;
32
+ line-height: 30px;
33
+ margin: 0;
34
+ }
35
+
36
+ .warning p {
37
+ font-size: 16px;
38
+ line-height: 25px;
39
+ font-weight: 400;
40
+ color: #555;
41
+ margin: 10px 0 0;
42
+ }
43
+
44
+ .warning p a {
45
+ color: #555;
46
+ }
47
+
48
+ @media screen and (min-width: 768px) {
49
+ body {
50
+ padding: 40px;
51
+ }
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div class="warning">
57
+ <h1>Insecure Connection<h1>
58
+ <p>The site at this URL requires an SSL-secured connection. Please visit <a href="${url}">${url}</a> instead. You will be automatically redirected in 15 seconds.</p>
59
+ </div>
60
+ <script>
61
+ setTimeout(() => {
62
+ location.href = '${url}';
63
+ }, 15 * 1000);
64
+ <\/script>
65
+ </body>
66
+ </html>
67
+ `;
68
+ };
69
+ export {
70
+ get_insecure_landing_page_html_default as default
71
+ };