@joystick.js/node-canary 0.0.0-canary.8 → 0.0.0-canary.80

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 (38) 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 +7 -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 +104 -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 -14
  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/package.json +2 -1
  35. package/dist/app/accounts/roles/index.test.js +0 -123
  36. package/dist/app/index.test.js +0 -575
  37. package/dist/app/middleware/sanitizeQueryParameters.js +0 -16
  38. 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
@@ -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,63 @@ 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
+ return res.status(200).send({
188
+ data: {
189
+ [data?.componentId]: data?.data
190
+ },
191
+ translations,
192
+ req: browserSafeRequest
193
+ });
194
+ }
195
+ res.status(200).send({ data: {}, translations: {} });
196
+ });
197
+ this.express.app.post("/api/_test/accounts/signup", async (req, res) => {
198
+ const existingUser = await runUserQuery("user", { emailAddress: req?.body?.emailAddress });
199
+ if (existingUser) {
200
+ await runUserQuery("deleteUser", { userId: existingUser?._id || existingUser?.user_id });
201
+ }
202
+ const signup = await accounts.signup({
203
+ emailAddress: req?.body?.emailAddress,
204
+ password: req?.body?.password,
205
+ metadata: req?.body?.metadata,
206
+ output: req?.body?.output || defaultUserOutputFields
207
+ });
208
+ res.status(200).send(JSON.stringify({
209
+ ...signup?.user || {},
210
+ joystickToken: signup?.token,
211
+ joystickLoginTokenExpiresAt: signup?.tokenExpiresAt
212
+ }));
213
+ });
214
+ this.express.app.post("/api/_test/accounts/login", async (req, res) => {
215
+ const login = await accounts.login({
216
+ emailAddress: req?.body?.emailAddress,
217
+ username: req?.body?.username,
218
+ password: req?.body?.password,
219
+ output: req?.body?.output || defaultUserOutputFields
220
+ });
221
+ res.status(200).send(JSON.stringify({
222
+ ...login?.user || {},
223
+ joystickToken: login?.token,
224
+ joystickLoginTokenExpiresAt: login?.tokenExpiresAt
225
+ }));
226
+ });
227
+ this.express.app.delete("/api/_test/accounts", async (req, res) => {
228
+ await runUserQuery("deleteUser", { userId: req?.body?.userId });
229
+ res.status(200).send({ data: {} });
230
+ });
231
+ }
232
+ }
159
233
  initDeploy() {
160
234
  if (process.env.NODE_ENV === "production" && process.env.IS_PUSH_DEPLOYED) {
161
235
  this.express.app.get("/api/_push/pre-version", async (req, res) => {
@@ -401,7 +475,7 @@ class App {
401
475
  });
402
476
  }
403
477
  initDevelopmentRoutes() {
404
- if (process.env.NODE_ENV === "development") {
478
+ if (["development", "test"].includes(process.env.NODE_ENV)) {
405
479
  this.express.app.get("/api/_joystick/sessions", async (req, res) => {
406
480
  const sessions = Array.from(this.sessions.entries())?.reduce((acc = {}, [key, value]) => {
407
481
  acc[key] = value;
@@ -431,11 +505,20 @@ class App {
431
505
  metadata: req?.body?.metadata,
432
506
  output: req?.body?.output || defaultUserOutputFields
433
507
  });
434
- accounts._setAuthenticationCookie(res, {
435
- token: signup?.token,
436
- tokenExpiresAt: signup?.tokenExpiresAt
437
- });
438
- res.status(200).send(JSON.stringify(signup?.user || {}));
508
+ if (!process.env.NODE_ENV !== "test") {
509
+ accounts._setAuthenticationCookie(res, {
510
+ token: signup?.token,
511
+ tokenExpiresAt: signup?.tokenExpiresAt
512
+ });
513
+ }
514
+ const response = {
515
+ ...signup?.user || {}
516
+ };
517
+ if (process.env.NODE_ENV === "test") {
518
+ response.joystickToken = signup?.token;
519
+ response.joystickLoginTokenExpiresAt = signup?.tokenExpiresAt;
520
+ }
521
+ res.status(200).send(JSON.stringify(response));
439
522
  } catch (exception) {
440
523
  console.log(exception);
441
524
  return res.status(500).send(JSON.stringify({
@@ -451,11 +534,20 @@ class App {
451
534
  password: req?.body?.password,
452
535
  output: req?.body?.output || defaultUserOutputFields
453
536
  });
454
- accounts._setAuthenticationCookie(res, {
455
- token: login?.token,
456
- tokenExpiresAt: login?.tokenExpiresAt
457
- });
458
- res.status(200).send(JSON.stringify(login?.user || {}));
537
+ if (!process.env.NODE_ENV !== "test") {
538
+ accounts._setAuthenticationCookie(res, {
539
+ token: login?.token,
540
+ tokenExpiresAt: login?.tokenExpiresAt
541
+ });
542
+ }
543
+ const response = {
544
+ ...login?.user || {}
545
+ };
546
+ if (process.env.NODE_ENV === "test") {
547
+ response.joystickToken = login?.token;
548
+ response.joystickLoginTokenExpiresAt = login?.tokenExpiresAt;
549
+ }
550
+ res.status(200).send(JSON.stringify(response));
459
551
  } catch (exception) {
460
552
  console.log(exception);
461
553
  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)) {