@joystick.js/node-canary 0.0.0-canary.31 → 0.0.0-canary.310

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 (91) 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/generate_sql_from_object.js +45 -0
  18. package/dist/app/databases/mongodb/buildConnectionString.js +1 -1
  19. package/dist/app/databases/mongodb/createAccountsIndexes.js +18 -0
  20. package/dist/app/databases/mongodb/createSessionsIndexes.js +10 -0
  21. package/dist/app/databases/mongodb/index.js +1 -1
  22. package/dist/app/databases/mongodb/queries/accounts.js +8 -1
  23. package/dist/app/databases/mongodb/queries/queues.js +40 -26
  24. package/dist/app/databases/mongodb/queries/sessions.js +26 -0
  25. package/dist/app/databases/postgresql/createSessionsIndexes.js +10 -0
  26. package/dist/app/databases/postgresql/createSessionsTables.js +14 -0
  27. package/dist/app/databases/postgresql/handleCleanupQueues.js +35 -0
  28. package/dist/app/databases/postgresql/index.js +66 -4
  29. package/dist/app/databases/postgresql/queries/accounts.js +5 -2
  30. package/dist/app/databases/postgresql/queries/queues.js +69 -36
  31. package/dist/app/databases/postgresql/queries/sessions.js +43 -0
  32. package/dist/app/databases/queryMap.js +6 -2
  33. package/dist/app/databases/stringToSnakeCase.js +6 -0
  34. package/dist/app/getBrowserSafeRequest.js +3 -2
  35. package/dist/app/index.js +222 -75
  36. package/dist/app/initExpress.js +1 -1
  37. package/dist/app/middleware/csp.js +2 -2
  38. package/dist/app/middleware/getTranslations.js +64 -0
  39. package/dist/app/middleware/get_insecure_landing_page_html.js +71 -0
  40. package/dist/app/middleware/hmr/client.js +13 -9
  41. package/dist/app/middleware/index.js +6 -5
  42. package/dist/app/middleware/insecure.js +3 -4
  43. package/dist/app/middleware/render.js +11 -68
  44. package/dist/app/middleware/session.js +12 -11
  45. package/dist/app/queues/index.js +64 -28
  46. package/dist/app/registerGetters.js +5 -6
  47. package/dist/app/registerSetters.js +5 -6
  48. package/dist/app/runGetter.js +17 -5
  49. package/dist/app/runSessionQuery.js +15 -0
  50. package/dist/app/runSetter.js +17 -5
  51. package/dist/app/sanitizeAPIResponse.js +1 -1
  52. package/dist/app/validateSession.js +8 -3
  53. package/dist/app/validateUploaderOptions.js +3 -3
  54. package/dist/app/validateUploads.js +12 -1
  55. package/dist/email/send.js +7 -1
  56. package/dist/email/templates/reset-password.js +0 -1
  57. package/dist/fixture/index.js +40 -0
  58. package/dist/index.js +19 -0
  59. package/dist/lib/escapeKeyValuePair.js +13 -0
  60. package/dist/lib/formatAPIError.js +0 -1
  61. package/dist/lib/getBuildPath.js +1 -1
  62. package/dist/lib/getSSLCertificates.js +3 -3
  63. package/dist/lib/importFile.js +7 -0
  64. package/dist/lib/isValidJSONString.js +1 -1
  65. package/dist/lib/log.js +0 -3
  66. package/dist/lib/objectToSQLKeysString.js +1 -1
  67. package/dist/lib/objectToSQLValuesString.js +1 -1
  68. package/dist/lib/serializeQueryParameters.js +1 -1
  69. package/dist/lib/timestamps.js +47 -0
  70. package/dist/lib/wait.js +8 -0
  71. package/dist/push/logs/index.js +10 -3
  72. package/dist/settings/load.js +3 -5
  73. package/dist/ssr/compileCSS.js +4 -4
  74. package/dist/ssr/findComponentInTree.js +1 -1
  75. package/dist/ssr/getAPIForDataFunctions.js +35 -0
  76. package/dist/ssr/getDataFromComponent.js +15 -0
  77. package/dist/ssr/index.js +19 -45
  78. package/dist/ssr/replaceWhenTags.js +2 -3
  79. package/dist/ssr/setHeadTagsInHTML.js +3 -3
  80. package/dist/test/index.js +9 -0
  81. package/dist/test/trackFunctionCall.js +17 -0
  82. package/dist/validation/inputWithSchema/index.js +3 -3
  83. package/dist/validation/schema/index.js +5 -5
  84. package/dist/websockets/index.js +4 -0
  85. package/getSanitizedContext.js +43 -0
  86. package/package.json +2 -1
  87. package/dist/app/accounts/roles/index.test.js +0 -123
  88. package/dist/app/index.test.js +0 -575
  89. package/dist/app/middleware/sanitizeRequestParameters.js +0 -21
  90. package/dist/email/send.test.js +0 -37
  91. package/dist/validation/index.test.js +0 -463
@@ -58,7 +58,7 @@ var client_default = (() => websocketClient({
58
58
  location.reload();
59
59
  }
60
60
  if (message?.settings) {
61
- window.__joystick_settings__ = JSON.stringify(message?.settings);
61
+ window.__joystick_settings__ = message?.settings;
62
62
  window.joystick.settings = message?.settings;
63
63
  }
64
64
  if (clientIndex) {
@@ -71,32 +71,36 @@ var client_default = (() => websocketClient({
71
71
  if (CSS) {
72
72
  const updatedCSS = document.createElement("link");
73
73
  updatedCSS.setAttribute("rel", "stylesheet");
74
- updatedCSS.setAttribute("href", "/_joystick/index.css");
74
+ updatedCSS.setAttribute("href", `/_joystick/index.css?v=${new Date().getTime()}`);
75
75
  document.head.appendChild(updatedCSS);
76
76
  }
77
77
  if (isFileChange && isPageInLayout) {
78
78
  (async () => {
79
79
  window.__joystick_childrenBeforeHMRUpdate__ = window.joystick?._internal?.tree?.instance?.children;
80
- const layoutComponentFile = await import(`${window.__joystick_layout__}?t=${new Date().getTime()}`);
81
- const pageComponentFile = await import(`${window.window.__joystick_layout_page_url__}?t=${new Date().getTime()}`);
80
+ const layoutComponentFile = await import(`${window.__joystick_layout__}?v=${new Date().getTime()}`).catch(() => {
81
+ location.reload();
82
+ });
83
+ const pageComponentFile = await import(`${window.window.__joystick_layout_page_url__}?t=${new Date().getTime()}`).catch(() => {
84
+ location.reload();
85
+ });
82
86
  const layout = layoutComponentFile.default;
83
87
  const page = pageComponentFile.default;
84
88
  window.joystick.mount(layout, Object.assign({ page }, window.__joystick_ssr_props__), document.getElementById("app"));
85
89
  if (connection.send) {
86
- const sessions = await fetch(`${location.origin}/api/_joystick/sessions`)?.then((response) => response.text());
87
- connection.send({ type: "HMR_UPDATE_COMPLETE", sessions });
90
+ connection.send({ type: "HMR_UPDATE_COMPLETE" });
88
91
  }
89
92
  })();
90
93
  }
91
94
  if (isFileChange && !isPageInLayout) {
92
95
  (async () => {
93
96
  window.__joystick_childrenBeforeHMRUpdate__ = window.joystick?._internal?.tree?.instance?.children;
94
- const pageComponentFile = await import(`${window.__joystick_page_url__}?t=${new Date().getTime()}`);
97
+ const pageComponentFile = await import(`${window.__joystick_page_url__}?v=${new Date().getTime()}`).catch(() => {
98
+ location.reload();
99
+ });
95
100
  const page = pageComponentFile.default;
96
101
  window.joystick.mount(page, Object.assign({}, window.__joystick_ssr_props__), document.getElementById("app"));
97
102
  if (connection.send) {
98
- const sessions = await fetch(`${location.origin}/api/_joystick/sessions`)?.then((response) => response.strin());
99
- connection.send({ type: "HMR_UPDATE_COMPLETE", sessions });
103
+ connection.send({ type: "HMR_UPDATE_COMPLETE" });
100
104
  }
101
105
  })();
102
106
  }
@@ -15,11 +15,10 @@ import runUserQuery from "../accounts/runUserQuery.js";
15
15
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
16
16
  import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
17
17
  import getBuildPath from "../../lib/getBuildPath.js";
18
- import sanitizeRequestParameters from "./sanitizeRequestParameters.js";
19
18
  import session from "./session.js";
20
19
  import csp from "./csp.js";
21
20
  const cwd = replaceFileProtocol(replaceBackslashesWithForwardSlashes(process.cwd()));
22
- const faviconPath = process.env.NODE_ENV === "test" ? `${cwd}/src/tests/mocks/app/public/favicon.ico` : "public/favicon.ico";
21
+ const faviconPath = "public/favicon.ico";
23
22
  var middleware_default = ({
24
23
  app,
25
24
  port,
@@ -27,7 +26,7 @@ var middleware_default = ({
27
26
  appInstance,
28
27
  cspConfig
29
28
  }) => {
30
- if (process.env.NODE_ENV === "production") {
29
+ if (process.env.NODE_ENV !== "development") {
31
30
  app.use(insecure);
32
31
  }
33
32
  const buildPath = getBuildPath();
@@ -42,8 +41,8 @@ var middleware_default = ({
42
41
  }
43
42
  next();
44
43
  });
45
- app.use(sanitizeRequestParameters);
46
44
  app.use(requestMethods);
45
+ app.enable("trust proxy");
47
46
  if (cspConfig) {
48
47
  app.use((req, res, next) => csp(req, res, next, cspConfig));
49
48
  }
@@ -73,7 +72,9 @@ var middleware_default = ({
73
72
  app.use(cookieParser());
74
73
  app.use(bodyParser(middlewareConfig?.bodyParser));
75
74
  app.use(cors(middlewareConfig?.cors, port));
76
- app.use((req, res, next) => session(req, res, next, appInstance));
75
+ if (process.databases?._sessions) {
76
+ app.use((req, res, next) => session(req, res, next));
77
+ }
77
78
  app.use(async (req, res, next) => {
78
79
  const loginTokenHasExpired = await hasLoginTokenExpired(res, req?.cookies?.joystickLoginToken, req?.cookies?.joystickLoginTokenExpiresAt);
79
80
  req.context = {
@@ -1,8 +1,7 @@
1
+ import get_insecure_landing_page_html from "./get_insecure_landing_page_html.js";
1
2
  var insecure_default = (req, res, next) => {
2
- const forwardedProtocol = req.get("x-forwarded-proto");
3
- const isForwardedFromHTTPS = forwardedProtocol && forwardedProtocol === "https";
4
- if (process.env.NODE_ENV != "development" && !req.secure && !isForwardedFromHTTPS) {
5
- return res.redirect("https://" + req.headers.host + req.url);
3
+ if (!req.secure) {
4
+ return res.send(get_insecure_landing_page_html(req?.headers?.host, req?.url));
6
5
  }
7
6
  next();
8
7
  };
@@ -8,6 +8,10 @@ import generateErrorPage from "../../lib/generateErrorPage.js";
8
8
  import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
9
9
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
10
10
  import getBuildPath from "../../lib/getBuildPath.js";
11
+ import escapeHTML from "../../lib/escapeHTML.js";
12
+ import escapeKeyValuePair from "../../lib/escapeKeyValuePair.js";
13
+ import importFile from "../../lib/importFile.js";
14
+ import getTranslations from "./getTranslations.js";
11
15
  const generateHash = (input = "") => {
12
16
  return crypto.createHash("sha256").update(input).digest("hex");
13
17
  };
@@ -65,71 +69,12 @@ const getCachedHTML = ({ cachePath, cacheFileName, currentDiff }) => {
65
69
  const getUrl = (request = {}) => {
66
70
  const [path = null] = request.url?.split("?");
67
71
  return {
68
- params: request.params,
69
- query: request.query,
70
- route: request.route.path,
71
- path
72
+ params: escapeKeyValuePair(request.params),
73
+ query: escapeKeyValuePair(request.query),
74
+ route: escapeHTML(request.route.path),
75
+ path: escapeHTML(path)
72
76
  };
73
77
  };
74
- const getFile = async (buildPath = "") => {
75
- const file = await import(buildPath);
76
- return file.default;
77
- };
78
- const getTranslationsFile = async (languageFilePath = "", paths = "") => {
79
- const languageFile = await getFile(`${paths.build}/i18n/${languageFilePath}`);
80
- const isValidLanguageFile = languageFile && isObject(languageFile);
81
- if (isValidLanguageFile) {
82
- const translationsForPage = languageFile[paths.page];
83
- return translationsForPage ? translationsForPage : languageFile;
84
- }
85
- return {};
86
- };
87
- const getTranslations = async (paths = {}, languagePreferences = []) => {
88
- const languageFiles = fs.readdirSync(`${paths.build}/i18n`);
89
- let matchingFile = null;
90
- for (let i = 0; i < languagePreferences.length; i += 1) {
91
- const languageRegex = languagePreferences[i];
92
- const match = languageFiles.find((languageFile) => !!languageFile.match(languageRegex));
93
- if (match) {
94
- matchingFile = match;
95
- break;
96
- }
97
- }
98
- const translationsFile = await getTranslationsFile(matchingFile, paths);
99
- return translationsFile;
100
- };
101
- const getLanguagePreferenceRegexes = (userLanguage = "", browserLanguages = []) => {
102
- let languagePreferences = [];
103
- if (userLanguage) {
104
- languagePreferences.push(userLanguage);
105
- }
106
- const filteredBrowserLanguages = browserLanguages?.filter((language) => {
107
- return !language?.includes("*");
108
- });
109
- languagePreferences.push(...filteredBrowserLanguages);
110
- languagePreferences.push(settings?.config?.i18n?.defaultLanguage);
111
- return languagePreferences?.flatMap((language) => {
112
- const variants = [language];
113
- if (language?.length === 2) {
114
- variants.push(`${language.substring(0, 2)}-`);
115
- }
116
- if (language?.length > 2) {
117
- variants.push(`${language?.split("-")[0]}`);
118
- variants.push(`${language?.split("-")[0]}-`);
119
- }
120
- return variants;
121
- })?.map((languageString) => {
122
- const lastCharacter = languageString[languageString.length - 1];
123
- if (lastCharacter === "-") {
124
- return new RegExp(`^${languageString}[A-Z]+.js`, "g");
125
- }
126
- return new RegExp(`^${languageString}.js`, "g");
127
- });
128
- };
129
- const parseBrowserLanguages = (languages = "") => {
130
- const rawLanguages = languages.split(",");
131
- return rawLanguages?.map((rawLanguage) => rawLanguage.split(";")[0]);
132
- };
133
78
  var render_default = (req, res, next, appInstance = {}) => {
134
79
  res.render = async function(path = "", options = {}) {
135
80
  const urlFormattedForCache = req?.url?.split("/")?.filter((part) => !!part)?.join("_");
@@ -167,13 +112,11 @@ var render_default = (req, res, next, appInstance = {}) => {
167
112
  return res.send(cachedHTML);
168
113
  }
169
114
  }
170
- const pageFile = await getFile(pagePath);
115
+ const pageFile = await importFile(pagePath);
171
116
  const Page = pageFile;
172
- const layoutFile = layoutPath ? await getFile(layoutPath) : null;
117
+ const layoutFile = layoutPath ? await importFile(layoutPath) : null;
173
118
  const Layout = layoutFile;
174
- const browserLanguages = parseBrowserLanguages(req?.headers["accept-language"]);
175
- const languagePreferenceRegexes = getLanguagePreferenceRegexes(req?.context?.user?.language, browserLanguages);
176
- const translations = await getTranslations({ build: buildPath, page: path }, languagePreferenceRegexes);
119
+ const translations = await getTranslations({ build: buildPath, page: path }, req);
177
120
  const url = getUrl(req);
178
121
  const props = { ...options?.props || {} };
179
122
  if (layoutPath && fs.existsSync(layoutPath)) {
@@ -1,21 +1,22 @@
1
1
  import setCookie from "../../lib/setCookie.js";
2
- import generateId from "../../lib/generateId.js";
3
- var session_default = (req, res, next, appInstance = {}) => {
4
- let sessionId = req?.cookies?.joystickSession;
5
- if (!sessionId) {
6
- sessionId = generateId(32);
7
- setCookie(res, "joystickSession", sessionId);
8
- }
9
- if (!appInstance.sessions.get(sessionId)) {
10
- appInstance.sessions.set(sessionId, { id: sessionId, csrf: generateId(32) });
2
+ import runSessionQuery from "../runSessionQuery.js";
3
+ var session_default = async (req, res, next) => {
4
+ await runSessionQuery("delete_expired_sessions");
5
+ let session_id = req?.cookies?.joystickSession;
6
+ const existing_session = session_id ? await runSessionQuery("get_session", {
7
+ session_id
8
+ }) : null;
9
+ if (!existing_session) {
10
+ session_id = await runSessionQuery("create_session");
11
+ setCookie(res, "joystickSession", session_id);
11
12
  }
12
13
  req.cookies = {
13
14
  ...req?.cookies || {},
14
- joystickSession: sessionId
15
+ joystickSession: session_id
15
16
  };
16
17
  req.context = {
17
18
  ...req?.context || {},
18
- session: appInstance?.sessions?.get(sessionId)
19
+ session: await runSessionQuery("get_session", { session_id })
19
20
  };
20
21
  next();
21
22
  };
@@ -1,28 +1,29 @@
1
- import dayjs from "dayjs";
2
1
  import fs from "fs";
3
2
  import os from "os";
4
3
  import generateId from "../../lib/generateId";
5
4
  import getTargetDatabaseProvider from "../databases/getTargetDatabaseProvider";
6
5
  import queryMap from "../databases/queryMap";
6
+ import chalk from "chalk";
7
+ import timestamps from "../../lib/timestamps";
7
8
  class Queue {
8
9
  constructor(queueName = "", queueOptions = {}) {
10
+ this._initDatabase = this._initDatabase.bind(this);
9
11
  this.machineId = fs.readFileSync(`${os.homedir()}/.cheatcode/MACHINE_ID`, "utf-8")?.trim().replace(/\n/g, "");
10
12
  this.name = queueName;
11
13
  this.options = {
12
14
  concurrentJobs: 1,
13
15
  ...queueOptions
14
16
  };
15
- this._initDatabase();
16
- if (queueOptions?.runOnStartup) {
17
- this.run();
18
- }
17
+ this._initDatabase(this?.options?.external, this?.options?.database?.provider);
19
18
  }
20
- async _initDatabase() {
21
- const queuesDatabase = getTargetDatabaseProvider("queues");
22
- const db = queryMap[queuesDatabase]?.queues;
23
- if (db && typeof db === "object" && !Array.isArray(db)) {
24
- this.db = Object.entries(db)?.reduce((boundQueries = {}, [queryFunctionName, queryFunction]) => {
19
+ async _initDatabase(is_external = false, database_provider = null) {
20
+ const queuesDatabase = database_provider || getTargetDatabaseProvider("queues");
21
+ const queue_queries_for_database_provider = queryMap[queuesDatabase]?.queues;
22
+ const db = this._getDatabaseConnection();
23
+ if (db && queue_queries_for_database_provider && typeof queue_queries_for_database_provider === "object" && !Array.isArray(queue_queries_for_database_provider)) {
24
+ this.db = Object.entries(queue_queries_for_database_provider || {})?.reduce((boundQueries = {}, [queryFunctionName, queryFunction]) => {
25
25
  boundQueries[queryFunctionName] = queryFunction.bind({
26
+ db,
26
27
  machineId: this.machineId,
27
28
  queue: {
28
29
  name: this.name,
@@ -31,16 +32,32 @@ class Queue {
31
32
  });
32
33
  return boundQueries;
33
34
  }, {});
34
- await this.db.initializeDatabase();
35
+ if (!is_external) {
36
+ await this.db.initializeDatabase(queuesDatabase);
37
+ if (this?.options?.runOnStartup) {
38
+ this.run();
39
+ }
40
+ }
35
41
  }
36
42
  }
43
+ _getDatabaseConnection() {
44
+ if (this?.options?.database) {
45
+ const { provider, name } = this?.options?.database;
46
+ const existing_connection = process.databases && process.databases[provider] && process.databases[provider][name];
47
+ if (!existing_connection) {
48
+ console.warn(chalk.red(`Connection to database ${provider}.${name} not found on process. Cannot start queue.`));
49
+ }
50
+ return existing_connection || null;
51
+ }
52
+ return process.databases._queues;
53
+ }
37
54
  add(options = {}) {
38
- const nextRunAt2 = options?.nextRunAt === "now" || !options?.nextRunAt ? dayjs().format() : options?.nextRunAt;
55
+ const nextRunAt = options?.nextRunAt === "now" || !options?.nextRunAt ? new Date().toISOString() : options?.nextRunAt;
39
56
  this.db.addJob({
40
57
  _id: generateId(),
41
58
  status: "pending",
42
59
  ...options,
43
- nextRunAt: nextRunAt2
60
+ nextRunAt
44
61
  });
45
62
  }
46
63
  async _checkIfOkayToRunJobs() {
@@ -55,7 +72,7 @@ class Queue {
55
72
  return;
56
73
  }
57
74
  if (!this.options.retryJobsRunningBeforeRestart) {
58
- return this.db.setJobsForMachineIncomplete();
75
+ return this.db.deleteIncompleteJobsForMachine();
59
76
  }
60
77
  return this.db.setJobsForMachinePending();
61
78
  }
@@ -69,41 +86,60 @@ class Queue {
69
86
  const okayToRunJobs = await this._checkIfOkayToRunJobs();
70
87
  if (okayToRunJobs && !process.env.HALT_QUEUES) {
71
88
  const nextJob = await this.db.getNextJobToRun();
72
- this._handleNextJob(nextJob);
89
+ this.handleNextJob(nextJob);
73
90
  }
74
91
  }, 300);
75
92
  });
76
93
  }
77
- _handleNextJob(nextJob = {}) {
78
- if (nextJob && nextJob?.job && this.options.jobs[nextJob?.job] && typeof this.options.jobs[nextJob?.job]?.run === "function") {
94
+ async handleNextJob(nextJob = {}) {
95
+ const job_definition = this.options.jobs[nextJob?.job];
96
+ if (nextJob && nextJob?.job && job_definition && typeof job_definition?.run === "function") {
79
97
  try {
80
- this.options.jobs[nextJob.job].run(nextJob?.payload, {
98
+ if (typeof job_definition?.preflight?.okayToRun === "function") {
99
+ const okay_to_run = await job_definition?.preflight?.okayToRun(nextJob?.payload, nextJob);
100
+ if (!okay_to_run) {
101
+ return this._handleRequeueJob(nextJob, timestamps.get_future_time("seconds", job_definition?.preflight?.requeueDelayInSeconds || 10));
102
+ }
103
+ }
104
+ if (!isNaN(job_definition?.maxAttempts)) {
105
+ if (nextJob?.attempts >= parseInt(job_definition?.maxAttempts, 10)) {
106
+ if (typeof job_definition?.onMaxAttemptsExhausted === "function") {
107
+ await job_definition.onMaxAttemptsExhausted(nextJob);
108
+ }
109
+ return this._handleDeleteJob(nextJob?._id);
110
+ }
111
+ }
112
+ await this._logAttempt(nextJob?._id);
113
+ await job_definition.run(nextJob?.payload, {
81
114
  ...nextJob,
82
115
  queue: this,
83
116
  completed: () => this._handleJobCompleted(nextJob?._id),
84
- failed: (error) => this._handleJobFailed(nextJob?._id, error),
117
+ failed: (error) => this._handleJobFailed(nextJob, job_definition, error),
85
118
  delete: () => this._handleDeleteJob(nextJob?._id),
86
- requeue: (nextRunAt2 = "") => this._handleRequeueJob(nextJob, nextRunAt2)
119
+ requeue: (nextRunAt = "") => this._handleRequeueJob(nextJob, nextRunAt)
87
120
  });
88
121
  } catch (exception) {
89
- this._handleJobFailed(nextJob?._id, exception);
90
- if (this.options.jobs[nextJob.job]?.requeueOnFailure) {
91
- this._handleRequeueJob(nextRunAt, dayjs().add(10, "seconds").format());
92
- }
122
+ this._handleJobFailed(nextJob, job_definition, exception);
93
123
  }
94
124
  }
95
125
  }
126
+ _logAttempt(jobId = "") {
127
+ return this.db.logAttempt(jobId);
128
+ }
96
129
  _handleJobCompleted(jobId = "") {
97
130
  return this.db.setJobCompleted(jobId);
98
131
  }
99
- _handleJobFailed(jobId = "", error = "") {
100
- return this.db.setJobFailed(jobId, error);
132
+ _handleJobFailed(nextJob = {}, job_definition = {}, error = "") {
133
+ if (job_definition?.requeueOnFailure) {
134
+ return this._handleRequeueJob(nextJob, timestamps.get_future_time("seconds", 10));
135
+ }
136
+ return this.db.setJobFailed(nextJob?._id, error);
101
137
  }
102
138
  _handleDeleteJob(jobId = "") {
103
139
  return this.db.deleteJob(jobId);
104
140
  }
105
- _handleRequeueJob(job = {}, nextRunAt2 = dayjs().format()) {
106
- return this.db.requeueJob(job?._id, nextRunAt2);
141
+ _handleRequeueJob(job = {}, nextRunAt = new Date().toISOString()) {
142
+ return this.db.requeueJob(job?._id, nextRunAt);
107
143
  }
108
144
  list(status = "") {
109
145
  const query = {};
@@ -1,10 +1,6 @@
1
1
  import getAPIURLComponent from "./getAPIURLComponent";
2
2
  import getAPIContext from "./getAPIContext";
3
3
  import formatAPIError from "../lib/formatAPIError";
4
- import validate from "../validation/index.js";
5
- import getOutput from "./getOutput";
6
- import sanitizeAPIResponse from "./sanitizeAPIResponse";
7
- import { isObject } from "../validation/lib/typeValidators";
8
4
  import validateSession from "./validateSession.js";
9
5
  import runGetter from "./runGetter.js";
10
6
  var registerGetters_default = (express, getters = [], context = {}, APIOptions = {}, appInstance = {}) => {
@@ -12,7 +8,7 @@ var registerGetters_default = (express, getters = [], context = {}, APIOptions =
12
8
  if (app) {
13
9
  for (const [getterName, getterOptions] of getters) {
14
10
  app.get(`/api/_getters/${getAPIURLComponent(getterName)}`, ...Array.isArray(getterOptions?.middleware) ? getterOptions?.middleware : [], async (req, res) => {
15
- const isValidSession = validateSession(req, res, appInstance?.sessions);
11
+ const isValidSession = await validateSession(req, res);
16
12
  if (!isValidSession) {
17
13
  return;
18
14
  }
@@ -30,7 +26,10 @@ var registerGetters_default = (express, getters = [], context = {}, APIOptions =
30
26
  return res.status(200).send(JSON.stringify(response));
31
27
  }).catch((error) => {
32
28
  if (typeof error === "string") {
33
- return res.status(500).send(error);
29
+ const sanitized_error = error?.replace("[runGetter] ", "")?.replace("[runGetter.handleRunGetter] ", "");
30
+ return res.status(500).send(JSON.stringify({
31
+ errors: [formatAPIError(new Error(sanitized_error))]
32
+ }));
34
33
  }
35
34
  if (typeof error === "object" && !Array.isArray(error)) {
36
35
  return res.status(error?.errors && error?.errors[0]?.status || 400).send(JSON.stringify(error));
@@ -1,10 +1,6 @@
1
1
  import getAPIURLComponent from "./getAPIURLComponent.js";
2
2
  import getAPIContext from "./getAPIContext.js";
3
3
  import formatAPIError from "../lib/formatAPIError.js";
4
- import validate from "../validation/index.js";
5
- import getOutput from "./getOutput.js";
6
- import sanitizeAPIResponse from "./sanitizeAPIResponse.js";
7
- import { isObject } from "../validation/lib/typeValidators.js";
8
4
  import validateSession from "./validateSession.js";
9
5
  import runSetter from "./runSetter.js";
10
6
  var registerSetters_default = (express, setters = [], context = {}, APIOptions = {}, appInstance = {}) => {
@@ -12,7 +8,7 @@ var registerSetters_default = (express, setters = [], context = {}, APIOptions =
12
8
  if (app) {
13
9
  for (const [setterName, setterOptions] of setters) {
14
10
  app.post(`/api/_setters/${getAPIURLComponent(setterName)}`, ...Array.isArray(setterOptions?.middleware) ? setterOptions?.middleware : [], async (req, res) => {
15
- const isValidSession = validateSession(req, res, appInstance?.sessions);
11
+ const isValidSession = await validateSession(req, res);
16
12
  if (!isValidSession) {
17
13
  return;
18
14
  }
@@ -30,7 +26,10 @@ var registerSetters_default = (express, setters = [], context = {}, APIOptions =
30
26
  return res.status(200).send(JSON.stringify(response));
31
27
  }).catch((error) => {
32
28
  if (typeof error === "string") {
33
- return res.status(500).send(error);
29
+ const sanitized_error = error?.replace("[runSetter] ", "")?.replace("[runSetter.handleRunSetter] ", "");
30
+ return res.status(500).send(JSON.stringify({
31
+ errors: [formatAPIError(new Error(sanitized_error))]
32
+ }));
34
33
  }
35
34
  if (typeof error === "object" && !Array.isArray(error)) {
36
35
  return res.status(error?.errors && error?.errors[0]?.status || 400).send(JSON.stringify(error));
@@ -3,19 +3,31 @@ import formatAPIError from "../lib/formatAPIError.js";
3
3
  import { isObject } from "../validation/lib/typeValidators.js";
4
4
  import getOutput from "./getOutput.js";
5
5
  import sanitizeAPIResponse from "./sanitizeAPIResponse.js";
6
- const handleRunGetter = async (getterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
6
+ import trackFunctionCall from "../test/trackFunctionCall.js";
7
+ import getSanitizedContext from "../../getSanitizedContext.js";
8
+ const handleRunGetter = async (name = "", getterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
7
9
  try {
8
10
  const shouldDisableSanitizationForGetter = getterOptions?.sanitize === false;
9
11
  const shouldSanitizeOutput = (getterOptions?.sanitize || APIOptions?.sanitize) === true || isObject(APIOptions?.sanitize || getterOptions?.sanitize);
10
- const data = await getterOptions?.get(input, context) || {};
12
+ const sanitizedContext = getSanitizedContext(context);
13
+ trackFunctionCall(`node.api.getters.${name}`, [
14
+ input,
15
+ sanitizedContext
16
+ ]);
17
+ const data = await getterOptions?.get(input, context);
11
18
  const response = output ? getOutput(data, output) : data;
12
19
  return !shouldDisableSanitizationForGetter && shouldSanitizeOutput ? sanitizeAPIResponse(response, getterOptions?.sanitize || APIOptions?.sanitize) : response;
13
20
  } catch (exception) {
14
21
  throw new Error(`[runGetter.handleRunGetter] ${exception.message}`);
15
22
  }
16
23
  };
17
- const handleRunAuthorization = async (getterOptions = {}, input = {}, context = {}) => {
24
+ const handleRunAuthorization = async (name = "", getterOptions = {}, input = {}, context = {}) => {
18
25
  try {
26
+ const sanitizedContext = getSanitizedContext(context);
27
+ trackFunctionCall(`node.api.getters.${name}.authorized`, [
28
+ input,
29
+ sanitizedContext
30
+ ]);
19
31
  const authorization = await getterOptions?.authorized(input, context);
20
32
  if (typeof authorization === "boolean") {
21
33
  return authorization;
@@ -77,7 +89,7 @@ const runGetter = async (options, { resolve, reject }) => {
77
89
  }
78
90
  }
79
91
  if (typeof options?.getterOptions?.authorized === "function") {
80
- const authorized = await handleRunAuthorization(options?.getterOptions, options?.input, options?.context);
92
+ const authorized = await handleRunAuthorization(options?.getterName, options?.getterOptions, options?.input, options?.context);
81
93
  if (!authorized || typeof authorized === "string") {
82
94
  return reject({
83
95
  errors: [
@@ -87,7 +99,7 @@ const runGetter = async (options, { resolve, reject }) => {
87
99
  }
88
100
  }
89
101
  if (typeof options?.getterOptions?.get === "function") {
90
- const response = await handleRunGetter(options?.getterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
102
+ const response = await handleRunGetter(options?.getterName, options?.getterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
91
103
  return resolve(response);
92
104
  }
93
105
  resolve();
@@ -0,0 +1,15 @@
1
+ import queryMap from "./databases/queryMap";
2
+ import getTargetDatabaseConnection from "./databases/getTargetDatabaseConnection.js";
3
+ var runSessionQuery_default = async (queryName = "", inputs = {}) => {
4
+ const sessionsDatabase = getTargetDatabaseConnection("sessions");
5
+ const queryMapForDatabase = sessionsDatabase && queryMap && queryMap[sessionsDatabase?.provider] && queryMap[sessionsDatabase?.provider]?.sessions;
6
+ const query = queryMapForDatabase && queryMapForDatabase[queryName];
7
+ if (sessionsDatabase?.connection && query) {
8
+ const response = await queryMapForDatabase[queryName](inputs, sessionsDatabase?.connection);
9
+ return Promise.resolve(response);
10
+ }
11
+ return null;
12
+ };
13
+ export {
14
+ runSessionQuery_default as default
15
+ };
@@ -3,19 +3,31 @@ import formatAPIError from "../lib/formatAPIError.js";
3
3
  import { isObject } from "../validation/lib/typeValidators.js";
4
4
  import getOutput from "./getOutput.js";
5
5
  import sanitizeAPIResponse from "./sanitizeAPIResponse.js";
6
- const handleRunSetter = async (setterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
6
+ import trackFunctionCall from "../test/trackFunctionCall.js";
7
+ import getSanitizedContext from "../../getSanitizedContext.js";
8
+ const handleRunSetter = async (name = "", setterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
7
9
  try {
8
10
  const shouldDisableSanitizationForSetter = setterOptions?.sanitize === false;
9
11
  const shouldSanitizeOutput = (setterOptions?.sanitize || APIOptions?.sanitize) === true || isObject(APIOptions?.sanitize || setterOptions?.sanitize);
10
- const data = await setterOptions?.set(input, context) || {};
12
+ const sanitizedContext = getSanitizedContext(context);
13
+ trackFunctionCall(`node.api.setters.${name}`, [
14
+ input,
15
+ sanitizedContext
16
+ ]);
17
+ const data = await setterOptions?.set(input, context);
11
18
  const response = output ? getOutput(data, output) : data;
12
19
  return !shouldDisableSanitizationForSetter && shouldSanitizeOutput ? sanitizeAPIResponse(response, setterOptions?.sanitize || APIOptions?.sanitize) : response;
13
20
  } catch (exception) {
14
21
  throw new Error(`[runSetter.handleRunSetter] ${exception.message}`);
15
22
  }
16
23
  };
17
- const handleRunAuthorization = async (getterOptions = {}, input = {}, context = {}) => {
24
+ const handleRunAuthorization = async (name = "", getterOptions = {}, input = {}, context = {}) => {
18
25
  try {
26
+ const sanitizedContext = getSanitizedContext(context);
27
+ trackFunctionCall(`node.api.setters.${name}.authorized`, [
28
+ input,
29
+ sanitizedContext
30
+ ]);
19
31
  const authorization = await getterOptions?.authorized(input, context);
20
32
  if (typeof authorization === "boolean") {
21
33
  return authorization;
@@ -77,7 +89,7 @@ const runSetter = async (options, { resolve, reject }) => {
77
89
  }
78
90
  }
79
91
  if (typeof options?.setterOptions?.authorized === "function") {
80
- const authorized = await handleRunAuthorization(options?.setterOptions, options?.input, options?.context);
92
+ const authorized = await handleRunAuthorization(options?.setterName, options?.setterOptions, options?.input, options?.context);
81
93
  if (!authorized || typeof authorized === "string") {
82
94
  return reject({
83
95
  errors: [
@@ -87,7 +99,7 @@ const runSetter = async (options, { resolve, reject }) => {
87
99
  }
88
100
  }
89
101
  if (typeof options?.setterOptions?.set === "function") {
90
- const response = await handleRunSetter(options?.setterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
102
+ const response = await handleRunSetter(options?.setterName, options?.setterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
91
103
  return resolve(response);
92
104
  }
93
105
  resolve();
@@ -9,7 +9,7 @@ const sanitizeAPIResponse = (data = null) => {
9
9
  sanitizedData = escapeHTML(sanitizedData)?.trim();
10
10
  }
11
11
  if (util.isObject(sanitizedData) && !Array.isArray(sanitizedData)) {
12
- sanitizedData = Object.entries(sanitizedData)?.reduce((result = {}, [key, value]) => {
12
+ sanitizedData = Object.entries(sanitizedData || {})?.reduce((result = {}, [key, value]) => {
13
13
  result[key] = sanitizeAPIResponse(value);
14
14
  return result;
15
15
  }, {});
@@ -1,8 +1,13 @@
1
1
  import formatAPIError from "../lib/formatAPIError.js";
2
- var validateSession_default = (req = null, res = null, sessions = null) => {
3
- const sessionToken = req?.cookies?.joystickSession;
2
+ import runSessionQuery from "./runSessionQuery.js";
3
+ var validateSession_default = async (req = null, res = null) => {
4
4
  const csrfToken = req?.headers["x-joystick-csrf"];
5
- const session = sessions?.get(sessionToken);
5
+ const session = await runSessionQuery("get_session", {
6
+ session_id: req?.cookies?.joystickSession
7
+ });
8
+ if (csrfToken === "joystick_test") {
9
+ return true;
10
+ }
6
11
  if (!session || session && session.csrf !== csrfToken) {
7
12
  res.status(403).send(JSON.stringify({
8
13
  errors: [formatAPIError(new Error("Unauthorized request."))]
@@ -20,7 +20,7 @@ const allowedS3Options = [
20
20
  ];
21
21
  var validateUploaderOptions_default = (options = {}) => {
22
22
  const errors = [];
23
- const optionNames = Object.keys(options);
23
+ const optionNames = Object.keys(options || {});
24
24
  optionNames.forEach((optionName) => {
25
25
  if (!allowedOptions.includes(optionName)) {
26
26
  errors.push(`${optionName} is not an allowed uploader option.`);
@@ -33,14 +33,14 @@ var validateUploaderOptions_default = (options = {}) => {
33
33
  errors.push(`If an uploader provider is 's3', s3 object must be specified with configuration.`);
34
34
  }
35
35
  if (options?.provider?.includes("local") && options.local) {
36
- Object.keys(options.local).forEach((optionName) => {
36
+ Object.keys(options.local || {}).forEach((optionName) => {
37
37
  if (!allowedLocalOptions.includes(optionName)) {
38
38
  errors.push(`local.${optionName} is not an allowed uploader option.`);
39
39
  }
40
40
  });
41
41
  }
42
42
  if (options?.provider?.includes("s3") && options.s3) {
43
- Object.keys(options.s3).forEach((optionName) => {
43
+ Object.keys(options.s3 || {}).forEach((optionName) => {
44
44
  if (!allowedS3Options.includes(optionName)) {
45
45
  errors.push(`s3.${optionName} is not an allowed uploader option.`);
46
46
  }