@joystick.js/node-canary 0.0.0-canary.9 → 0.0.0-canary.90

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 (39) hide show
  1. package/_package.json +2 -1
  2. package/dist/app/accounts/createMetadataTableColumns.js +12 -0
  3. package/dist/app/accounts/deleteUser.js +7 -0
  4. package/dist/app/accounts/generateSession.js +2 -4
  5. package/dist/app/accounts/getBrowserSafeUser.js +2 -1
  6. package/dist/app/accounts/hasLoginTokenExpired.js +1 -2
  7. package/dist/app/accounts/index.js +2 -0
  8. package/dist/app/accounts/signup.js +37 -7
  9. package/dist/app/databases/mongodb/createAccountsIndexes.js +18 -0
  10. package/dist/app/databases/mongodb/index.js +1 -1
  11. package/dist/app/databases/mongodb/queries/accounts.js +8 -0
  12. package/dist/app/databases/postgresql/queries/accounts.js +3 -0
  13. package/dist/app/databases/stringToSnakeCase.js +6 -0
  14. package/dist/app/getBrowserSafeRequest.js +3 -2
  15. package/dist/app/index.js +102 -12
  16. package/dist/app/middleware/getTranslations.js +64 -0
  17. package/dist/app/middleware/index.js +4 -5
  18. package/dist/app/middleware/render.js +11 -68
  19. package/dist/app/sanitizeAPIResponse.js +1 -6
  20. package/dist/app/validateSession.js +3 -0
  21. package/dist/email/templates/reset-password.js +0 -1
  22. package/dist/index.js +7 -2
  23. package/dist/lib/escapeHTML.js +9 -0
  24. package/dist/lib/escapeKeyValuePair.js +13 -0
  25. package/dist/lib/getBuildPath.js +1 -1
  26. package/dist/lib/getSSLCertificates.js +1 -1
  27. package/dist/lib/importFile.js +7 -0
  28. package/dist/lib/log.js +0 -3
  29. package/dist/lib/nodeUrlPolyfills.js +7 -15
  30. package/dist/lib/wait.js +8 -0
  31. package/dist/ssr/getAPIForDataFunctions.js +33 -0
  32. package/dist/ssr/getDataFromComponent.js +14 -0
  33. package/dist/ssr/index.js +5 -41
  34. package/dist/ssr/replaceWhenTags.js +2 -3
  35. package/package.json +2 -1
  36. package/dist/app/accounts/roles/index.test.js +0 -123
  37. package/dist/app/index.test.js +0 -575
  38. package/dist/app/middleware/sanitizeQueryParameters.js +0 -16
  39. package/dist/email/send.test.js +0 -37
package/_package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joystick.js/node",
3
- "version": "1.0.0-beta.165",
3
+ "version": "1.0.0-beta.166",
4
4
  "developmentVersion": "1.0.0-beta.1419",
5
5
  "type": "module",
6
6
  "description": "A Node.js framework for building web apps.",
@@ -39,6 +39,7 @@
39
39
  "node-html-parser": "^5.1.0",
40
40
  "nodemailer": "^6.7.0",
41
41
  "pg": "^8.7.3",
42
+ "pg-escape": "^0.2.0",
42
43
  "process": "^0.11.10",
43
44
  "query-string": "^7.0.1",
44
45
  "sanitize-html": "^2.7.3",
@@ -0,0 +1,12 @@
1
+ import escape from "pg-escape";
2
+ var createMetadataTableColumns_default = async (usersDatabase = "", sqlizedMetadata = {}) => {
3
+ if (usersDatabase === "postgresql") {
4
+ const columns = Object.keys(sqlizedMetadata);
5
+ for (let i = 0; i < columns?.length; i += 1) {
6
+ await process.databases.postgresql.query(escape(`ALTER TABLE users ADD COLUMN IF NOT EXISTS %I TEXT;`, columns[i]));
7
+ }
8
+ }
9
+ };
10
+ export {
11
+ createMetadataTableColumns_default as default
12
+ };
@@ -0,0 +1,7 @@
1
+ import runUserQuery from "./runUserQuery.js";
2
+ var deleteUser_default = (userId = "") => {
3
+ return runUserQuery("deleteUser", { userId });
4
+ };
5
+ export {
6
+ deleteUser_default as default
7
+ };
@@ -1,9 +1,7 @@
1
1
  import dayjs from "dayjs";
2
+ import utc from "dayjs/plugin/utc.js";
2
3
  import generateId from "../../lib/generateId";
3
- if (process.env.NODE_ENV !== "test") {
4
- const utc = await import("dayjs/plugin/utc");
5
- dayjs.extend(utc.default);
6
- }
4
+ dayjs.extend(utc);
7
5
  var generateSession_default = () => {
8
6
  return {
9
7
  token: generateId(64),
@@ -1,4 +1,5 @@
1
1
  import { isObject } from "../../validation/lib/typeValidators";
2
+ import escapeKeyValuePair from "../../lib/escapeKeyValuePair.js";
2
3
  var getBrowserSafeUser_default = (user = null) => {
3
4
  if (!user || !isObject(user)) {
4
5
  return null;
@@ -16,7 +17,7 @@ var getBrowserSafeUser_default = (user = null) => {
16
17
  return fields;
17
18
  }
18
19
  }, {});
19
- return browserSafeUser;
20
+ return escapeKeyValuePair(browserSafeUser);
20
21
  };
21
22
  export {
22
23
  getBrowserSafeUser_default as default
@@ -5,8 +5,7 @@ var hasLoginTokenExpired_default = async (res, token = null, tokenExpiresAt = nu
5
5
  unsetAuthenticationCookie(res);
6
6
  return true;
7
7
  }
8
- const _dayjs = process.env.NODE_ENV === "test" ? (await import("../../tests/mocks/dayjs")).default : null;
9
- const hasExpired = process.env.NODE_ENV === "test" ? _dayjs().isAfter(_dayjs(tokenExpiresAt)) : dayjs().isAfter(dayjs(tokenExpiresAt));
8
+ const hasExpired = dayjs().isAfter(dayjs(tokenExpiresAt));
10
9
  if (hasExpired) {
11
10
  unsetAuthenticationCookie(res);
12
11
  return true;
@@ -1,6 +1,7 @@
1
1
  import _setAuthenticationCookie from "./setAuthenticationCookie.js";
2
2
  import _unsetAuthenticationCookie from "./unsetAuthenticationCookie.js";
3
3
  import defaultUserOutputFields from "./defaultUserOutputFields";
4
+ import deleteUser from "./deleteUser.js";
4
5
  import getBrowserSafeUser from "./getBrowserSafeUser";
5
6
  import login from "./login";
6
7
  import recoverPassword from "./recoverPassword";
@@ -14,6 +15,7 @@ var accounts_default = {
14
15
  _setAuthenticationCookie,
15
16
  _unsetAuthenticationCookie,
16
17
  defaultUserOutputFields,
18
+ deleteUser,
17
19
  getBrowserSafeUser,
18
20
  login,
19
21
  recoverPassword,
@@ -6,6 +6,9 @@ import { isObject } from "../../validation/lib/typeValidators";
6
6
  import getOutput from "../getOutput";
7
7
  import typesMap from "../databases/typesMap";
8
8
  import getTargetDatabaseProvider from "../databases/getTargetDatabaseProvider.js";
9
+ import stringToSnakeCase from "../databases/stringToSnakeCase.js";
10
+ import createMetadataTableColumns from "./createMetadataTableColumns.js";
11
+ import roles from "./roles/index.js";
9
12
  const addSessionToUser = (userId = null, session = null) => {
10
13
  try {
11
14
  return runUserQuery("addSession", { userId, session });
@@ -27,6 +30,16 @@ const insertUserInDatabase = async (user = {}) => {
27
30
  throw new Error(formatErrorString("signup.insertUserInDatabase", exception));
28
31
  }
29
32
  };
33
+ const sqlizeMetadata = (metadata = {}) => {
34
+ try {
35
+ return Object.entries(metadata).reduce((sqlized = {}, [key, value]) => {
36
+ sqlized[stringToSnakeCase(key)] = value;
37
+ return sqlized;
38
+ }, {});
39
+ } catch (exception) {
40
+ throw new Error(`[actionName.sqlizeMetadata] ${exception.message}`);
41
+ }
42
+ };
30
43
  const getUserToCreate = async (options = {}) => {
31
44
  try {
32
45
  const usersDatabase = getTargetDatabaseProvider("users");
@@ -40,12 +53,19 @@ const getUserToCreate = async (options = {}) => {
40
53
  if (options?.username) {
41
54
  user.username = options?.username;
42
55
  }
43
- if (options?.metadata && isObject(options.metadata) && usersDatabaseType === "sql" && options?.metadata?.language) {
44
- user.language = options?.metadata?.language;
56
+ if (options?.metadata && isObject(options.metadata) && usersDatabaseType === "sql") {
57
+ const sqlizedMetadata = sqlizeMetadata(options.metadata);
58
+ await createMetadataTableColumns(usersDatabase, sqlizedMetadata);
59
+ const { roles: [], ...restOfMetadata } = options?.metadata;
60
+ user = {
61
+ ...sqlizeMetadata(restOfMetadata),
62
+ ...user
63
+ };
45
64
  }
46
65
  if (options?.metadata && isObject(options.metadata) && usersDatabaseType === "nosql") {
66
+ const { roles: [], ...restOfMetadata } = options?.metadata;
47
67
  user = {
48
- ...options.metadata,
68
+ ...restOfMetadata || {},
49
69
  ...user
50
70
  };
51
71
  }
@@ -70,15 +90,25 @@ const signup = async (options, { resolve, reject }) => {
70
90
  return reject("Password is required.");
71
91
  }
72
92
  const existingUser = await getExistingUser(options.emailAddress, options.metadata?.username);
73
- if (existingUser) {
93
+ if (existingUser && process.env.NODE_ENV !== "test") {
74
94
  throw new Error(`A user with the ${existingUser.existingUsername ? "username" : "email address"} ${existingUser.existingUsername || existingUser.existingEmailAddress} already exists.`);
75
95
  }
76
- const userToCreate = await getUserToCreate(options);
77
- const userId = await insertUserInDatabase(userToCreate);
96
+ let userToCreate;
97
+ let userId = existingUser?._id || existingUser?.user_id;
98
+ if (!existingUser) {
99
+ userToCreate = await getUserToCreate(options);
100
+ userId = await insertUserInDatabase(userToCreate);
101
+ }
78
102
  const user = await getUserByUserId(userId);
79
103
  const session = generateSession();
80
104
  if (user?._id || user?.user_id) {
81
- await addSessionToUser(user._id || user?.user_id, session);
105
+ await addSessionToUser(user?._id || user?.user_id, session);
106
+ }
107
+ if (options?.metadata?.roles?.length > 0 && process.env.NODE_ENV === "test") {
108
+ for (let i = 0; i < options?.metadata?.roles?.length; i += 1) {
109
+ const role = options?.metadata?.roles[i];
110
+ roles.grant(user?._id || user?.user_id, role);
111
+ }
82
112
  }
83
113
  return resolve({
84
114
  ...session,
@@ -0,0 +1,18 @@
1
+ const createIndex = async (collectionName = "", index = {}) => {
2
+ return process.databases._users?.collection(collectionName).createIndex(index);
3
+ };
4
+ var createAccountsIndexes_default = async () => {
5
+ await createIndex("users", { _id: 1 });
6
+ await createIndex("users", { emailAddress: 1 });
7
+ await createIndex("users", { username: 1 });
8
+ await createIndex("users", { "sessions.token": 1 });
9
+ await createIndex("users", { "passwordResetTokens.token": 1 });
10
+ await createIndex("users", { "passwordResetTokens.token": 1, _id: 1 });
11
+ await createIndex("users", { roles: 1 });
12
+ await createIndex("users", { roles: 1, _id: 1 });
13
+ await createIndex("roles", { role: 1 });
14
+ await createIndex("roles", { role: 1, userId: 1 });
15
+ };
16
+ export {
17
+ createAccountsIndexes_default as default
18
+ };
@@ -17,7 +17,7 @@ var mongodb_default = async (settings = {}, databasePort = 2610) => {
17
17
  const connectionOptions = {
18
18
  useNewUrlParser: true,
19
19
  useUnifiedTopology: true,
20
- ssl: !process.env.NODE_ENV?.includes("development"),
20
+ ssl: !["development", "test"].includes(process.env.NODE_ENV),
21
21
  ...settings?.options || {}
22
22
  };
23
23
  if (settings?.options?.ca) {
@@ -1,5 +1,7 @@
1
1
  import generateId from "../../../../lib/generateId";
2
2
  import dayjs from "dayjs";
3
+ import utc from "dayjs/plugin/utc.js";
4
+ dayjs.extend(utc);
3
5
  var accounts_default = {
4
6
  existingUser: async (input = {}) => {
5
7
  let existingUserWithEmailAddress;
@@ -35,6 +37,11 @@ var accounts_default = {
35
37
  }
36
38
  return null;
37
39
  },
40
+ deleteUser: async (input = {}) => {
41
+ return process.databases._users?.collection("users").deleteOne({
42
+ _id: input?.userId
43
+ });
44
+ },
38
45
  deleteOldSessions: async (input = {}) => {
39
46
  const user = await process.databases._users?.collection("users").findOne({
40
47
  _id: input?.userId
@@ -65,6 +72,7 @@ var accounts_default = {
65
72
  const user = await process.databases._users?.collection("users").findOne({
66
73
  "sessions.token": input?.token
67
74
  });
75
+ console.log({ userWithLoginToken: user });
68
76
  return user;
69
77
  },
70
78
  createVerifyEmailToken: async (input) => {
@@ -43,6 +43,9 @@ var accounts_default = {
43
43
  }
44
44
  return null;
45
45
  },
46
+ deleteUser: async (input = {}) => {
47
+ await process.databases._users?.query(`DELETE FROM users WHERE user_id = $1`, [input?.userId]);
48
+ },
46
49
  deleteOldSessions: async (input = {}) => {
47
50
  await process.databases._users?.query(`DELETE FROM users_sessions WHERE user_id = $1 AND token_expires_at::date < NOW()`, [input?.userId]);
48
51
  },
@@ -0,0 +1,6 @@
1
+ var stringToSnakeCase_default = (string = "") => {
2
+ return string?.split(/\.?(?=[A-Z])/).join("_").toLowerCase();
3
+ };
4
+ export {
5
+ stringToSnakeCase_default as default
6
+ };
@@ -1,8 +1,9 @@
1
1
  import getBrowserSafeUser from "./accounts/getBrowserSafeUser";
2
+ import escapeKeyValuePair from "../lib/escapeKeyValuePair.js";
2
3
  var getBrowserSafeRequest_default = (req = {}) => {
3
4
  const browserSafeRequest = {};
4
- browserSafeRequest.params = req.params;
5
- browserSafeRequest.query = req.query;
5
+ browserSafeRequest.params = escapeKeyValuePair(req.params);
6
+ browserSafeRequest.query = escapeKeyValuePair(req.query);
6
7
  browserSafeRequest.context = {
7
8
  user: getBrowserSafeUser(req.context.user)
8
9
  };
package/dist/app/index.js CHANGED
@@ -25,6 +25,7 @@ import runUploader from "./runUploader";
25
25
  import generateId from "../lib/generateId.js";
26
26
  import getOutput from "./getOutput.js";
27
27
  import defaultUserOutputFields from "./accounts/defaultUserOutputFields.js";
28
+ import createMongoDBAccountsIndexes from "./databases/mongodb/createAccountsIndexes";
28
29
  import createPostgreSQLAccountsTables from "./databases/postgresql/createAccountsTables";
29
30
  import createPostgreSQLAccountsIndexes from "./databases/postgresql/createAccountsIndexes";
30
31
  import loadSettings from "../settings/load.js";
@@ -32,8 +33,15 @@ import Queue from "./queues/index.js";
32
33
  import readDirectory from "../lib/readDirectory.js";
33
34
  import getBuildPath from "../lib/getBuildPath.js";
34
35
  import generateMachineId from "../lib/generateMachineId.js";
36
+ import importFile from "../lib/importFile.js";
35
37
  import emitWebsocketEvent from "../websockets/emitWebsocketEvent.js";
36
38
  import getTargetDatabaseConnection from "./databases/getTargetDatabaseConnection.js";
39
+ import getAPIForDataFunctions from "../ssr/getAPIForDataFunctions.js";
40
+ import getBrowserSafeRequest from "./getBrowserSafeRequest.js";
41
+ import getDataFromComponent from "../ssr/getDataFromComponent.js";
42
+ import getTranslations from "./middleware/getTranslations.js";
43
+ import runUserQuery from "./accounts/runUserQuery.js";
44
+ import wait from "../lib/wait.js";
37
45
  process.setMaxListeners(0);
38
46
  class App {
39
47
  constructor(options = {}) {
@@ -53,6 +61,7 @@ class App {
53
61
  this.initWebsockets(options?.websockets || {});
54
62
  this.initDevelopmentRoutes();
55
63
  this.initAccounts();
64
+ this.initTests();
56
65
  this.initDeploy();
57
66
  this.initAPI(options?.api);
58
67
  this.initRoutes(options?.routes);
@@ -85,6 +94,9 @@ class App {
85
94
  const hasQueuesDatabase = settings?.config?.databases?.some((database = {}) => {
86
95
  return !!database?.queues;
87
96
  });
97
+ const hasMongoDBUsersDatabase = settings?.config?.databases?.some((database = {}) => {
98
+ return database?.provider === "mongodb" && database?.users;
99
+ });
88
100
  const hasPostgreSQLUsersDatabase = settings?.config?.databases?.some((database = {}) => {
89
101
  return database?.provider === "postgresql" && database?.users;
90
102
  });
@@ -131,6 +143,9 @@ class App {
131
143
  if (hasQueuesDatabase) {
132
144
  process.databases._queues = getTargetDatabaseConnection("queues")?.connection;
133
145
  }
146
+ if (hasMongoDBUsersDatabase) {
147
+ await createMongoDBAccountsIndexes();
148
+ }
134
149
  if (hasPostgreSQLUsersDatabase) {
135
150
  await createPostgreSQLAccountsTables();
136
151
  await createPostgreSQLAccountsIndexes();
@@ -143,7 +158,9 @@ class App {
143
158
  process.BUILD_ERROR = JSON.parse(message);
144
159
  }
145
160
  });
146
- console.log(`App running at: http://localhost:${express.port}`);
161
+ if (process.env.NODE_ENV !== "test") {
162
+ console.log(`App running at: http://localhost:${express.port}`);
163
+ }
147
164
  }
148
165
  setMachineId() {
149
166
  generateMachineId();
@@ -156,6 +173,61 @@ class App {
156
173
  fs.writeFileSync("./.joystick/PROCESS_ID", `${generateId(32)}`);
157
174
  }
158
175
  }
176
+ initTests() {
177
+ if (process.env.NODE_ENV === "test") {
178
+ this.express.app.get("/api/_test/bootstrap", async (req, res) => {
179
+ const buildPath = `${process.cwd()}/.joystick/build`;
180
+ const Component = req?.query?.pathToComponent ? await importFile(`${buildPath}/${req?.query?.pathToComponent}`) : null;
181
+ if (Component) {
182
+ const componentInstance = Component();
183
+ const apiForDataFunctions = await getAPIForDataFunctions(req, this?.options?.api);
184
+ const browserSafeRequest = getBrowserSafeRequest(req);
185
+ const data = await getDataFromComponent(componentInstance, apiForDataFunctions, browserSafeRequest);
186
+ const translations = await getTranslations({ build: buildPath, page: req?.query?.pathToComponent }, req);
187
+ const settings = loadSettings();
188
+ return res.status(200).send({
189
+ data: {
190
+ [data?.componentId]: data?.data
191
+ },
192
+ req: browserSafeRequest,
193
+ settings,
194
+ translations
195
+ });
196
+ }
197
+ res.status(200).send({ data: {}, translations: {} });
198
+ });
199
+ this.express.app.post("/api/_test/accounts/signup", async (req, res) => {
200
+ const signup = await accounts.signup({
201
+ emailAddress: req?.body?.emailAddress,
202
+ password: req?.body?.password,
203
+ metadata: req?.body?.metadata,
204
+ output: req?.body?.output || defaultUserOutputFields
205
+ });
206
+ res.status(200).send(JSON.stringify({
207
+ ...signup?.user || {},
208
+ joystickLoginToken: signup?.token,
209
+ joystickLoginTokenExpiresAt: signup?.tokenExpiresAt
210
+ }));
211
+ });
212
+ this.express.app.post("/api/_test/accounts/login", async (req, res) => {
213
+ const login = await accounts.login({
214
+ emailAddress: req?.body?.emailAddress,
215
+ username: req?.body?.username,
216
+ password: req?.body?.password,
217
+ output: req?.body?.output || defaultUserOutputFields
218
+ });
219
+ res.status(200).send(JSON.stringify({
220
+ ...login?.user || {},
221
+ joystickLoginToken: login?.token,
222
+ joystickLoginTokenExpiresAt: login?.tokenExpiresAt
223
+ }));
224
+ });
225
+ this.express.app.delete("/api/_test/accounts", async (req, res) => {
226
+ await runUserQuery("deleteUser", { userId: req?.body?.userId });
227
+ res.status(200).send({ data: {} });
228
+ });
229
+ }
230
+ }
159
231
  initDeploy() {
160
232
  if (process.env.NODE_ENV === "production" && process.env.IS_PUSH_DEPLOYED) {
161
233
  this.express.app.get("/api/_push/pre-version", async (req, res) => {
@@ -401,7 +473,7 @@ class App {
401
473
  });
402
474
  }
403
475
  initDevelopmentRoutes() {
404
- if (process.env.NODE_ENV === "development") {
476
+ if (["development", "test"].includes(process.env.NODE_ENV)) {
405
477
  this.express.app.get("/api/_joystick/sessions", async (req, res) => {
406
478
  const sessions = Array.from(this.sessions.entries())?.reduce((acc = {}, [key, value]) => {
407
479
  acc[key] = value;
@@ -431,11 +503,20 @@ class App {
431
503
  metadata: req?.body?.metadata,
432
504
  output: req?.body?.output || defaultUserOutputFields
433
505
  });
434
- accounts._setAuthenticationCookie(res, {
435
- token: signup?.token,
436
- tokenExpiresAt: signup?.tokenExpiresAt
437
- });
438
- res.status(200).send(JSON.stringify(signup?.user || {}));
506
+ if (!process.env.NODE_ENV !== "test") {
507
+ accounts._setAuthenticationCookie(res, {
508
+ token: signup?.token,
509
+ tokenExpiresAt: signup?.tokenExpiresAt
510
+ });
511
+ }
512
+ const response = {
513
+ ...signup?.user || {}
514
+ };
515
+ if (process.env.NODE_ENV === "test") {
516
+ response.joystickToken = signup?.token;
517
+ response.joystickLoginTokenExpiresAt = signup?.tokenExpiresAt;
518
+ }
519
+ res.status(200).send(JSON.stringify(response));
439
520
  } catch (exception) {
440
521
  console.log(exception);
441
522
  return res.status(500).send(JSON.stringify({
@@ -451,11 +532,20 @@ class App {
451
532
  password: req?.body?.password,
452
533
  output: req?.body?.output || defaultUserOutputFields
453
534
  });
454
- accounts._setAuthenticationCookie(res, {
455
- token: login?.token,
456
- tokenExpiresAt: login?.tokenExpiresAt
457
- });
458
- res.status(200).send(JSON.stringify(login?.user || {}));
535
+ if (!process.env.NODE_ENV !== "test") {
536
+ accounts._setAuthenticationCookie(res, {
537
+ token: login?.token,
538
+ tokenExpiresAt: login?.tokenExpiresAt
539
+ });
540
+ }
541
+ const response = {
542
+ ...login?.user || {}
543
+ };
544
+ if (process.env.NODE_ENV === "test") {
545
+ response.joystickToken = login?.token;
546
+ response.joystickLoginTokenExpiresAt = login?.tokenExpiresAt;
547
+ }
548
+ res.status(200).send(JSON.stringify(response));
459
549
  } catch (exception) {
460
550
  console.log(exception);
461
551
  return res.status(500).send(JSON.stringify({
@@ -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
+ };
@@ -3,6 +3,7 @@ import compression from "compression";
3
3
  import cookieParser from "cookie-parser";
4
4
  import favicon from "serve-favicon";
5
5
  import fs from "fs";
6
+ import { __package } from "../../index.js";
6
7
  import insecure from "./insecure.js";
7
8
  import requestMethods from "./requestMethods.js";
8
9
  import bodyParser from "./bodyParser.js";
@@ -14,11 +15,10 @@ import runUserQuery from "../accounts/runUserQuery.js";
14
15
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
15
16
  import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
16
17
  import getBuildPath from "../../lib/getBuildPath.js";
17
- import sanitizeQueryParameters from "./sanitizeQueryParameters.js";
18
18
  import session from "./session.js";
19
19
  import csp from "./csp.js";
20
20
  const cwd = replaceFileProtocol(replaceBackslashesWithForwardSlashes(process.cwd()));
21
- const faviconPath = process.env.NODE_ENV === "test" ? `${cwd}/src/tests/mocks/app/public/favicon.ico` : "public/favicon.ico";
21
+ const faviconPath = "public/favicon.ico";
22
22
  var middleware_default = ({
23
23
  app,
24
24
  port,
@@ -41,7 +41,6 @@ var middleware_default = ({
41
41
  }
42
42
  next();
43
43
  });
44
- app.use(sanitizeQueryParameters);
45
44
  app.use(requestMethods);
46
45
  if (cspConfig) {
47
46
  app.use((req, res, next) => csp(req, res, next, cspConfig));
@@ -54,7 +53,7 @@ var middleware_default = ({
54
53
  });
55
54
  app.use("/_joystick/utils/process.js", (_req, res) => {
56
55
  res.set("Content-Type", "text/javascript");
57
- const processPolyfill = fs.readFileSync(`${cwd}/node_modules/@joystick.js/node/dist/app/utils/process.js`, "utf-8");
56
+ const processPolyfill = fs.readFileSync(`${__package}/app/utils/process.js`, "utf-8");
58
57
  res.send(processPolyfill.replace("${NODE_ENV}", process.env.NODE_ENV));
59
58
  });
60
59
  app.use("/_joystick/index.client.js", express.static(`${buildPath}index.client.js`, {
@@ -65,7 +64,7 @@ var middleware_default = ({
65
64
  app.use("/_joystick/ui", express.static(`${buildPath}ui`, { eTag: false, maxAge: "0" }));
66
65
  app.use("/_joystick/hmr/client.js", (_req, res) => {
67
66
  res.set("Content-Type", "text/javascript");
68
- const hmrClient = fs.readFileSync(`${cwd}/node_modules/@joystick.js/node/dist/app/middleware/hmr/client.js`, "utf-8");
67
+ const hmrClient = fs.readFileSync(`${__package}/app/middleware/hmr/client.js`, "utf-8");
69
68
  res.send(hmrClient.replace("${process.env.PORT}", parseInt(process.env.PORT, 10) + 1));
70
69
  });
71
70
  app.use(favicon(faviconPath));
@@ -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)) {