@saltcorn/server 0.9.6-beta.8 → 0.9.6

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 (47) hide show
  1. package/app.js +9 -2
  2. package/auth/admin.js +51 -52
  3. package/auth/roleadmin.js +6 -2
  4. package/auth/routes.js +28 -10
  5. package/auth/testhelp.js +86 -0
  6. package/help/Field label.tmd +11 -0
  7. package/help/Field types.tmd +39 -0
  8. package/help/Inclusion Formula.tmd +38 -0
  9. package/help/Ownership field.tmd +76 -0
  10. package/help/Ownership formula.tmd +75 -0
  11. package/help/Table roles.tmd +20 -0
  12. package/help/User groups.tmd +35 -0
  13. package/help/User roles.tmd +30 -0
  14. package/load_plugins.js +28 -4
  15. package/locales/en.json +28 -1
  16. package/locales/it.json +3 -2
  17. package/markup/forms.js +5 -1
  18. package/package.json +9 -9
  19. package/public/log_viewer_utils.js +32 -0
  20. package/public/mermaid.min.js +705 -306
  21. package/public/saltcorn-builder.css +23 -0
  22. package/public/saltcorn-common.js +195 -71
  23. package/public/saltcorn.css +72 -0
  24. package/public/saltcorn.js +78 -0
  25. package/restart_watcher.js +1 -0
  26. package/routes/actions.js +27 -0
  27. package/routes/admin.js +180 -66
  28. package/routes/api.js +6 -0
  29. package/routes/common_lists.js +42 -32
  30. package/routes/fields.js +9 -1
  31. package/routes/homepage.js +2 -0
  32. package/routes/menu.js +69 -4
  33. package/routes/notifications.js +90 -10
  34. package/routes/pageedit.js +18 -13
  35. package/routes/plugins.js +5 -1
  36. package/routes/search.js +10 -4
  37. package/routes/tables.js +47 -27
  38. package/routes/tenant.js +4 -15
  39. package/routes/utils.js +20 -6
  40. package/routes/viewedit.js +11 -7
  41. package/serve.js +27 -5
  42. package/tests/edit.test.js +426 -0
  43. package/tests/fields.test.js +21 -0
  44. package/tests/filter.test.js +68 -0
  45. package/tests/page.test.js +2 -2
  46. package/tests/sync.test.js +59 -0
  47. package/wrapper.js +4 -1
@@ -13,7 +13,7 @@ const { getState } = require("@saltcorn/data/db/state");
13
13
  const Form = require("@saltcorn/data/models/form");
14
14
  const File = require("@saltcorn/data/models/file");
15
15
  const User = require("@saltcorn/data/models/user");
16
- const { renderForm } = require("@saltcorn/markup");
16
+ const { renderForm, post_btn } = require("@saltcorn/markup");
17
17
 
18
18
  const router = new Router();
19
19
  module.exports = router;
@@ -31,22 +31,50 @@ router.get(
31
31
  "/",
32
32
  loggedIn,
33
33
  error_catcher(async (req, res) => {
34
- const nots = await Notification.find(
35
- { user_id: req.user.id },
36
- { orderBy: "id", orderDesc: true, limit: 20 }
37
- );
34
+ const { after } = req.query;
35
+ const where = { user_id: req.user.id };
36
+ if (after) where.id = { lt: after };
37
+ const nots = await Notification.find(where, {
38
+ orderBy: "id",
39
+ orderDesc: true,
40
+ limit: 20,
41
+ });
38
42
  await Notification.mark_as_read({
39
43
  id: { in: nots.filter((n) => !n.read).map((n) => n.id) },
40
44
  });
45
+ const form = notificationSettingsForm();
46
+ const user = await User.findOne({ id: req.user?.id });
47
+ form.values = { notify_email: user?._attributes?.notify_email };
41
48
  const notifyCards = nots.length
42
49
  ? nots.map((not) => ({
43
50
  type: "card",
44
51
  class: [!not.read && "unread-notify"],
52
+ id: `notify-${not.id}`,
45
53
  contents: [
46
54
  div(
47
55
  { class: "d-flex" },
48
56
  span({ class: "fw-bold" }, not.title),
49
- span({ class: "ms-2 text-muted" }, moment(not.created).fromNow())
57
+ span(
58
+ {
59
+ class: "ms-2 text-muted",
60
+ title: not.created.toLocaleString(req.getLocale()),
61
+ },
62
+ moment(not.created).fromNow()
63
+ ),
64
+ div(
65
+ { class: "ms-auto" },
66
+ post_btn(
67
+ `/notifications/delete/${not.id}`,
68
+ "",
69
+ req.csrfToken(),
70
+ {
71
+ icon: "fas fa-times-circle",
72
+ klass: "btn-link text-muted text-decoration-none p-0",
73
+ ajax: true,
74
+ onClick: `$('#notify-${not.id}').remove()`,
75
+ }
76
+ )
77
+ )
50
78
  ),
51
79
  not.body && p(not.body),
52
80
  not.link && a({ href: not.link }, "Link"),
@@ -58,6 +86,35 @@ router.get(
58
86
  contents: [h5(req.__("No notifications"))],
59
87
  },
60
88
  ];
89
+ const pageLinks = div(
90
+ { class: "d-flex mt-3 mb-3" },
91
+ nots.length == 20
92
+ ? div(
93
+ after &&
94
+ a(
95
+ { href: `/notifications`, class: "me-2" },
96
+ "← " + req.__("Newest")
97
+ ),
98
+ a(
99
+ { href: `/notifications?after=${nots[19].id}` },
100
+ req.__("Older") + " →"
101
+ )
102
+ )
103
+ : div(),
104
+ nots.length > 0 &&
105
+ div(
106
+ { class: "ms-auto" },
107
+ post_btn(
108
+ `/notifications/delete/read`,
109
+ req.__("Delete all read"),
110
+ req.csrfToken(),
111
+ {
112
+ icon: "fas fa-trash",
113
+ klass: "btn-sm btn-danger",
114
+ }
115
+ )
116
+ )
117
+ );
61
118
  res.sendWrap(req.__("Notifications"), {
62
119
  above: [
63
120
  {
@@ -72,10 +129,10 @@ router.get(
72
129
  type: "card",
73
130
  contents: [
74
131
  req.__("Receive notifications by:"),
75
- renderForm(notificationSettingsForm(), req.csrfToken()),
132
+ renderForm(form, req.csrfToken()),
76
133
  ],
77
134
  },
78
- { above: notifyCards },
135
+ { above: [...notifyCards, pageLinks] },
79
136
  ],
80
137
  },
81
138
  ],
@@ -109,9 +166,27 @@ router.post(
109
166
  })
110
167
  );
111
168
 
169
+ router.post(
170
+ "/delete/:idlike",
171
+ loggedIn,
172
+ error_catcher(async (req, res) => {
173
+ const { idlike } = req.params;
174
+ if (idlike == "read") {
175
+ await Notification.deleteRead(req.user.id);
176
+ } else {
177
+ const id = +idlike;
178
+ const notif = await Notification.findOne({ id });
179
+ if (notif?.user_id == req.user?.id) await notif.delete();
180
+ }
181
+ if (req.xhr) res.json({ success: "ok" });
182
+ else res.redirect("/notifications");
183
+ })
184
+ );
185
+
112
186
  router.get(
113
- "/manifest.json",
187
+ "/manifest.json:opt_cache_bust?",
114
188
  error_catcher(async (req, res) => {
189
+ const { pretty } = req.query;
115
190
  const state = getState();
116
191
  const manifest = {
117
192
  name: state.getConfig("site_name"),
@@ -142,6 +217,11 @@ router.get(
142
217
  "red"
143
218
  );
144
219
  }
145
- res.json(manifest);
220
+ if (!pretty) res.json(manifest);
221
+ else {
222
+ const prettyJson = JSON.stringify(manifest, null, 2);
223
+ res.setHeader("Content-Type", "application/json");
224
+ res.send(prettyJson);
225
+ }
146
226
  })
147
227
  );
@@ -94,7 +94,7 @@ const pagePropertiesForm = async (req, isNew) => {
94
94
  if (groups.includes(s) && isNew)
95
95
  return req.__("A page group with this name already exists");
96
96
  },
97
- sublabel: req.__("A short name that will be in your URL"),
97
+ sublabel: req.__("A short name that will be in the page URL"),
98
98
  type: "String",
99
99
  attributes: { autofocus: true },
100
100
  }),
@@ -107,13 +107,15 @@ const pagePropertiesForm = async (req, isNew) => {
107
107
  new Field({
108
108
  label: req.__("Description"),
109
109
  name: "description",
110
- sublabel: req.__("A longer description"),
110
+ sublabel: req.__(
111
+ "A longer description that is not visible but appears in the page header and is indexed by search engines"
112
+ ),
111
113
  input_type: "text",
112
114
  }),
113
115
  {
114
116
  name: "min_role",
115
117
  label: req.__("Minimum role"),
116
- sublabel: req.__("Role required to access page"),
118
+ sublabel: req.__("User role required to access page"),
117
119
  input_type: "select",
118
120
  options: roles.map((r) => ({ value: r.id, label: r.role })),
119
121
  },
@@ -336,6 +338,15 @@ router.get(
336
338
  )
337
339
  ),
338
340
  },
341
+ {
342
+ type: "card",
343
+ title: req.__("Root pages"),
344
+ titleAjaxIndicator: true,
345
+ contents: renderForm(
346
+ getRootPageForm(pages, pageGroups, roles, req),
347
+ req.csrfToken()
348
+ ),
349
+ },
339
350
  {
340
351
  type: "card",
341
352
  title: req.__("Your page groups"),
@@ -357,15 +368,6 @@ router.get(
357
368
  )
358
369
  ),
359
370
  },
360
- {
361
- type: "card",
362
- title: req.__("Root pages"),
363
- titleAjaxIndicator: true,
364
- contents: renderForm(
365
- getRootPageForm(pages, pageGroups, roles, req),
366
- req.csrfToken()
367
- ),
368
- },
369
371
  ],
370
372
  });
371
373
  })
@@ -392,6 +394,7 @@ const wrap = (contents, noCard, req, page) => ({
392
394
  {
393
395
  type: noCard ? "container" : "card",
394
396
  title: page ? page.name : req.__("New"),
397
+ titleAjaxIndicator: true,
395
398
  contents,
396
399
  },
397
400
  ],
@@ -418,6 +421,7 @@ router.get(
418
421
  form.hidden("id");
419
422
  form.values = page;
420
423
  form.values.no_menu = page.attributes?.no_menu;
424
+ form.onChange = `saveAndContinue(this)`;
421
425
  res.sendWrap(
422
426
  req.__(`Page attributes`),
423
427
  wrap(renderForm(form, req.csrfToken()), false, req, page)
@@ -477,7 +481,8 @@ router.post(
477
481
  pageRow.layout = {};
478
482
  }
479
483
  await Page.update(+id, pageRow);
480
- res.redirect(`/pageedit/`);
484
+ if (req.xhr) res.json({ success: "ok" });
485
+ else res.redirect(`/pageedit/`);
481
486
  } else {
482
487
  if (!pageRow.layout) pageRow.layout = {};
483
488
  if (!pageRow.fixed_states) pageRow.fixed_states = {};
package/routes/plugins.js CHANGED
@@ -865,9 +865,13 @@ router.get(
865
865
  if (!module) {
866
866
  module = getState().plugins[getState().plugin_module_names[plugin.name]];
867
867
  }
868
+ const userLayout =
869
+ user._attributes?.layout?.plugin === plugin.name
870
+ ? user._attributes.layout.config || {}
871
+ : {};
868
872
  const form = await module.user_config_form({
869
873
  ...(plugin.configuration || {}),
870
- ...(user._attributes?.layout?.config || {}),
874
+ ...userLayout,
871
875
  });
872
876
  form.action = `/plugins/user_configure/${encodeURIComponent(plugin.name)}`;
873
877
  form.onChange = `applyViewConfig(this, '/plugins/user_saveconfig/${encodeURIComponent(
package/routes/search.js CHANGED
@@ -202,7 +202,8 @@ const runSearch = async ({ q, _page, table }, req, res) => {
202
202
  let tablesWithResults = [];
203
203
  let tablesConfigured = 0;
204
204
  for (const [tableName, viewName] of Object.entries(cfg)) {
205
- if (!viewName || viewName === "") continue;
205
+ if (!viewName || viewName === "" || viewName === "search_table_description")
206
+ continue;
206
207
  tablesConfigured += 1;
207
208
  if (table && tableName !== table) continue;
208
209
  let sectionHeader = tableName;
@@ -232,7 +233,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
232
233
  }
233
234
 
234
235
  if (vresps.length > 0) {
235
- tablesWithResults.push(tableName);
236
+ tablesWithResults.push({ tableName, label: sectionHeader });
236
237
  resp.push({
237
238
  type: "card",
238
239
  title: span({ id: tableName }, sectionHeader),
@@ -273,8 +274,13 @@ const runSearch = async ({ q, _page, table }, req, res) => {
273
274
  req.__("Show only matches in table:"),
274
275
  " ",
275
276
  tablesWithResults
276
- .map((t) =>
277
- a({ href: `javascript:set_state_field('table', '${t}')` }, t)
277
+ .map(({ tableName, label }) =>
278
+ a(
279
+ {
280
+ href: `javascript:set_state_field('table', '${tableName}')`,
281
+ },
282
+ label
283
+ )
278
284
  )
279
285
  .join(" | ")
280
286
  )
package/routes/tables.js CHANGED
@@ -93,14 +93,45 @@ const tableForm = async (table, req) => {
93
93
  noSubmitButton: true,
94
94
  onChange: "saveAndContinue(this)",
95
95
  fields: [
96
+ {
97
+ label: req.__("Minimum role to read"),
98
+ sublabel: req.__(
99
+ "User must have this role or higher to read rows from the table, unless they are the owner"
100
+ ),
101
+ help: {
102
+ topic: "Table roles",
103
+ context: {},
104
+ },
105
+ name: "min_role_read",
106
+ input_type: "select",
107
+ options: roleOptions,
108
+ attributes: { asideNext: !table.external && !table.provider_name },
109
+ },
96
110
  ...(!table.external && !table.provider_name
97
111
  ? [
112
+ {
113
+ label: req.__("Minimum role to write"),
114
+ name: "min_role_write",
115
+ input_type: "select",
116
+ help: {
117
+ topic: "Table roles",
118
+ context: {},
119
+ },
120
+ sublabel: req.__(
121
+ "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
122
+ ),
123
+ options: roleOptions,
124
+ },
98
125
  {
99
126
  label: req.__("Ownership field"),
100
127
  name: "ownership_field_id",
101
128
  sublabel: req.__(
102
129
  "The user referred to in this field will be the owner of the row"
103
130
  ),
131
+ help: {
132
+ topic: "Ownership field",
133
+ context: {},
134
+ },
104
135
  input_type: "select",
105
136
  options: [
106
137
  { value: "", label: req.__("None") },
@@ -114,6 +145,10 @@ const tableForm = async (table, req) => {
114
145
  validator: expressionValidator,
115
146
  type: "String",
116
147
  class: "validate-expression",
148
+ help: {
149
+ topic: "Ownership formula",
150
+ context: {},
151
+ },
117
152
  sublabel:
118
153
  req.__("User is treated as owner if true. In scope: ") +
119
154
  ["user", ...fields.map((f) => f.name)]
@@ -126,6 +161,10 @@ const tableForm = async (table, req) => {
126
161
  sublabel: req.__(
127
162
  "Add relations to this table in dropdown options for ownership field"
128
163
  ),
164
+ help: {
165
+ topic: "User groups",
166
+ context: {},
167
+ },
129
168
  name: "is_user_group",
130
169
  type: "Bool",
131
170
  },
@@ -141,28 +180,9 @@ const tableForm = async (table, req) => {
141
180
  ),
142
181
  //options: roleOptions,
143
182
  },
144
- {
145
- label: req.__("Minimum role to read"),
146
- sublabel: req.__(
147
- "User must have this role or higher to read rows from the table, unless they are the owner"
148
- ),
149
- name: "min_role_read",
150
- input_type: "select",
151
- options: roleOptions,
152
- attributes: { asideNext: !table.external && !table.provider_name },
153
- },
154
183
  ...(table.external || table.provider_name
155
184
  ? []
156
185
  : [
157
- {
158
- label: req.__("Minimum role to write"),
159
- name: "min_role_write",
160
- input_type: "select",
161
- sublabel: req.__(
162
- "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
163
- ),
164
- options: roleOptions,
165
- },
166
186
  {
167
187
  label: req.__("Version history"),
168
188
  sublabel: req.__("Track table data changes over time"),
@@ -754,6 +774,14 @@ router.get(
754
774
  r.typename +
755
775
  span({ class: "badge bg-danger ms-1" }, "Unknown type"),
756
776
  },
777
+ ...(table.external
778
+ ? []
779
+ : [
780
+ {
781
+ label: req.__("Edit"),
782
+ key: (r) => link(`/field/${r.id}`, req.__("Edit")),
783
+ },
784
+ ]),
757
785
  {
758
786
  label: "",
759
787
  key: (r) => typeBadges(r, req),
@@ -763,14 +791,6 @@ router.get(
763
791
  key: (r) => attribBadges(r),
764
792
  },
765
793
  { label: req.__("Variable name"), key: (t) => code(t.name) },
766
- ...(table.external
767
- ? []
768
- : [
769
- {
770
- label: req.__("Edit"),
771
- key: (r) => link(`/field/${r.id}`, req.__("Edit")),
772
- },
773
- ]),
774
794
  ...(table.external || db.isSQLite
775
795
  ? []
776
796
  : [
package/routes/tenant.js CHANGED
@@ -44,7 +44,7 @@ const {
44
44
  const db = require("@saltcorn/data/db");
45
45
 
46
46
  const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
47
- const { isAdmin, error_catcher } = require("./utils.js");
47
+ const { isAdmin, error_catcher, is_ip_address } = require("./utils.js");
48
48
  const User = require("@saltcorn/data/models/user");
49
49
  const File = require("@saltcorn/data/models/file");
50
50
  const {
@@ -117,22 +117,10 @@ const create_tenant_allowed = (req) => {
117
117
  return user_role <= required_role;
118
118
  };
119
119
 
120
- /**
121
- * Check that String is IPv4 address
122
- * @param {string} hostname
123
- * @returns {boolean|string[]}
124
- */
125
- // TBD not sure that false is correct return if type of is not string
126
- // TBD Add IPv6 support
127
- const is_ip_address = (hostname) => {
128
- if (typeof hostname !== "string") return false;
129
- return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
130
- };
131
-
132
120
  const get_cfg_tenant_base_url = (req) =>
133
121
  remove_leading_chars(
134
122
  ".",
135
- getRootState().getConfig("tenant_baseurl", req.hostname)
123
+ getRootState().getConfig("tenant_baseurl", req.hostname) || req.hostname
136
124
  )
137
125
  .replace("http://", "")
138
126
  .replace("https://", "");
@@ -268,7 +256,8 @@ router.post(
268
256
  return;
269
257
  }
270
258
  // declare ui form
271
- const form = tenant_form(req);
259
+ const base_url = get_cfg_tenant_base_url(req);
260
+ const form = tenant_form(req, base_url);
272
261
  // validate ui form
273
262
  const valres = form.validate(req.body);
274
263
  if (valres.errors)
package/routes/utils.js CHANGED
@@ -77,11 +77,9 @@ function loggedIn(req, res, next) {
77
77
  * @returns {void}
78
78
  */
79
79
  function isAdmin(req, res, next) {
80
- if (
81
- req.user &&
82
- req.user.role_id === 1 &&
83
- req.user.tenant === db.getTenantSchema()
84
- ) {
80
+ const cur_tenant = db.getTenantSchema();
81
+ //console.log({ cur_tenant, user: req.user });
82
+ if (req.user && req.user.role_id === 1 && req.user.tenant === cur_tenant) {
85
83
  next();
86
84
  } else {
87
85
  req.flash("danger", req.__("Must be admin"));
@@ -157,6 +155,8 @@ const get_tenant_from_req = (req, hostPartsOffset) => {
157
155
  if (req.subdomains && req.subdomains.length == 0)
158
156
  return db.connectObj.default_schema;
159
157
  if (!req.subdomains && req.headers.host) {
158
+ if (is_ip_address(req.headers.host.split(":")[0]))
159
+ return db.connectObj.default_schema;
160
160
  const parts = req.headers.host.split(".");
161
161
  if (parts.length < (!hostPartsOffset ? 3 : 3 - hostPartsOffset))
162
162
  return db.connectObj.default_schema;
@@ -299,7 +299,7 @@ const getGitRevision = () => db.connectObj.git_commit;
299
299
  * Gets session store
300
300
  * @returns {session|cookieSession}
301
301
  */
302
- const getSessionStore = () => {
302
+ const getSessionStore = (pruneInterval) => {
303
303
  /*if (getState().getConfig("cookie_sessions", false)) {
304
304
  return cookieSession({
305
305
  keys: [db.connectObj.session_secret || is.str.generate()],
@@ -322,6 +322,7 @@ const getSessionStore = () => {
322
322
  schemaName: db.connectObj.default_schema,
323
323
  pool: db.pool,
324
324
  tableName: "_sc_session",
325
+ pruneSessionInterval: pruneInterval > 0 ? pruneInterval : false,
325
326
  }),
326
327
  secret: db.connectObj.session_secret || is.str.generate(),
327
328
  resave: false,
@@ -350,6 +351,18 @@ const is_relative_url = (url) => {
350
351
  return typeof url === "string" && !url.includes(":/") && !url.includes("//");
351
352
  };
352
353
 
354
+ /**
355
+ * Check that String is IPv4 address
356
+ * @param {string} hostname
357
+ * @returns {boolean|string[]}
358
+ */
359
+ // TBD not sure that false is correct return if type of is not string
360
+ // TBD Add IPv6 support
361
+ const is_ip_address = (hostname) => {
362
+ if (typeof hostname !== "string") return false;
363
+ return hostname.split(".").every((s) => +s >= 0 && +s <= 255);
364
+ };
365
+
353
366
  const admin_config_route = ({
354
367
  router,
355
368
  path,
@@ -558,6 +571,7 @@ module.exports = {
558
571
  get_tenant_from_req,
559
572
  addOnDoneRedirect,
560
573
  is_relative_url,
574
+ is_ip_address,
561
575
  get_sys_info,
562
576
  admin_config_route,
563
577
  sendHtmlFile,
@@ -371,6 +371,7 @@ router.get(
371
371
  const form = await viewForm(req, tableOptions, roles, pages, viewrow);
372
372
  const inbound_connected = await viewrow.inbound_connected_objects();
373
373
  form.hidden("id");
374
+ form.onChange = `saveAndContinue(this)`;
374
375
  res.sendWrap(req.__(`Edit view`), {
375
376
  above: [
376
377
  {
@@ -383,6 +384,7 @@ router.get(
383
384
  {
384
385
  type: "card",
385
386
  class: "mt-0",
387
+ titleAjaxIndicator: true,
386
388
  title: req.__(
387
389
  `%s view - %s on %s`,
388
390
  viewname,
@@ -541,12 +543,14 @@ router.post(
541
543
  //console.log(v);
542
544
  await View.create(v);
543
545
  }
544
- res.redirect(
545
- addOnDoneRedirect(
546
- `/viewedit/config/${encodeURIComponent(v.name)}`,
547
- req
548
- )
549
- );
546
+ if (req.xhr) res.json({ success: "ok" });
547
+ else
548
+ res.redirect(
549
+ addOnDoneRedirect(
550
+ `/viewedit/config/${encodeURIComponent(v.name)}`,
551
+ req
552
+ )
553
+ );
550
554
  }
551
555
  } else {
552
556
  sendForm(form);
@@ -902,7 +906,7 @@ router.post(
902
906
  ? `/${req.query.on_done_redirect}`
903
907
  : "/viewedit";
904
908
  res.redirect(redirectTarget);
905
- } else res.json({ okay: true, responseText: message });
909
+ } else res.json({ success: "ok" });
906
910
  })
907
911
  );
908
912
 
package/serve.js CHANGED
@@ -235,6 +235,10 @@ module.exports =
235
235
  : defaultNCPUs;
236
236
 
237
237
  const letsEncrypt = await getConfig("letsencrypt", false);
238
+ const pruneSessionInterval = +(await getConfig(
239
+ "prune_session_interval",
240
+ 900
241
+ ));
238
242
  const masterState = {
239
243
  started: false,
240
244
  listeningTo: new Set([]),
@@ -287,7 +291,11 @@ module.exports =
287
291
  })
288
292
  .ready((glx) => {
289
293
  const httpsServer = glx.httpsServer();
290
- setupSocket(appargs?.subdomainOffset, httpsServer);
294
+ setupSocket(
295
+ appargs?.subdomainOffset,
296
+ pruneSessionInterval,
297
+ httpsServer
298
+ );
291
299
  httpsServer.setTimeout(timeout * 1000);
292
300
  process.on("message", workerDispatchMsg);
293
301
  glx.serveApp(app);
@@ -344,6 +352,10 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
344
352
  const cert = getState().getConfig("custom_ssl_certificate", "");
345
353
  const key = getState().getConfig("custom_ssl_private_key", "");
346
354
  const timeout = +getState().getConfig("timeout", 120);
355
+ const pruneSessionInterval = +(await getState().getConfig(
356
+ "prune_session_interval",
357
+ 900
358
+ ));
347
359
  // Server with http on port 80 / https on 443
348
360
  // todo resolve hardcode
349
361
  if (port === 80 && cert && key) {
@@ -354,7 +366,12 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
354
366
  // todo timeout to config
355
367
  httpServer.setTimeout(timeout * 1000);
356
368
  httpsServer.setTimeout(timeout * 1000);
357
- setupSocket(appargs?.subdomainOffset, httpServer, httpsServer);
369
+ setupSocket(
370
+ appargs?.subdomainOffset,
371
+ pruneSessionInterval,
372
+ httpServer,
373
+ httpsServer
374
+ );
358
375
  httpServer.listen(port, () => {
359
376
  console.log("HTTP Server running on port 80");
360
377
  });
@@ -367,7 +384,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
367
384
  // server with http only
368
385
  const http = require("http");
369
386
  const httpServer = http.createServer(app);
370
- setupSocket(appargs?.subdomainOffset, httpServer);
387
+ setupSocket(appargs?.subdomainOffset, pruneSessionInterval, httpServer);
371
388
 
372
389
  // todo timeout to config
373
390
  // todo refer in doc to httpserver doc
@@ -384,7 +401,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
384
401
  *
385
402
  * @param {...*} servers
386
403
  */
387
- const setupSocket = (subdomainOffset, ...servers) => {
404
+ const setupSocket = (subdomainOffset, pruneSessionInterval, ...servers) => {
388
405
  // https://socket.io/docs/v4/middlewares/
389
406
  const wrap = (middleware) => (socket, next) =>
390
407
  middleware(socket.request, {}, next);
@@ -395,7 +412,7 @@ const setupSocket = (subdomainOffset, ...servers) => {
395
412
  }
396
413
 
397
414
  const passportInit = passport.initialize();
398
- const sessionStore = getSessionStore();
415
+ const sessionStore = getSessionStore(pruneSessionInterval);
399
416
  const setupNamespace = (namespace) => {
400
417
  //io.of(namespace).use(wrap(setTenant));
401
418
  io.of(namespace).use(wrap(sessionStore));
@@ -452,6 +469,11 @@ const setupSocket = (subdomainOffset, ...servers) => {
452
469
  socketIds.push(socket.id);
453
470
  await getState().setConfig("joined_log_socket_ids", [...socketIds]);
454
471
  callback({ status: "ok" });
472
+ setTimeout(() => {
473
+ io.of("/")
474
+ .to(`_logs_${tenant}_`)
475
+ .emit("test_conn_msg", { text: "test message" });
476
+ }, 1000);
455
477
  }
456
478
  } catch (err) {
457
479
  getState().log(1, `Socket join_logs stream: ${err.stack}`);