@saltcorn/mobile-app 1.1.0-beta.9 → 1.1.0

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 (40) hide show
  1. package/.babelrc +3 -0
  2. package/build_scripts/modify_android_manifest.js +47 -0
  3. package/build_scripts/modify_gradle_cfg.js +21 -0
  4. package/package.json +21 -12
  5. package/src/.eslintrc +21 -0
  6. package/src/helpers/api.js +43 -0
  7. package/src/helpers/auth.js +191 -0
  8. package/src/helpers/common.js +189 -0
  9. package/{www/js/utils/table_utils.js → src/helpers/db_schema.js} +18 -40
  10. package/src/helpers/file_system.js +102 -0
  11. package/{www/js/utils/global_utils.js → src/helpers/navigation.js} +189 -332
  12. package/src/helpers/offline_mode.js +645 -0
  13. package/src/index.js +20 -0
  14. package/src/init.js +424 -0
  15. package/src/routing/index.js +98 -0
  16. package/{www/js → src/routing}/mocks/request.js +5 -5
  17. package/{www/js → src/routing}/mocks/response.js +1 -1
  18. package/{www/js → src/routing}/routes/api.js +10 -15
  19. package/{www/js → src/routing}/routes/auth.js +12 -6
  20. package/{www/js → src/routing}/routes/delete.js +9 -6
  21. package/{www/js → src/routing}/routes/edit.js +9 -6
  22. package/src/routing/routes/error.js +6 -0
  23. package/{www/js → src/routing}/routes/fields.js +7 -2
  24. package/{www/js → src/routing}/routes/page.js +15 -10
  25. package/{www/js → src/routing}/routes/sync.js +17 -8
  26. package/{www/js → src/routing}/routes/view.js +20 -15
  27. package/{www/js/routes/common.js → src/routing/utils.js} +18 -13
  28. package/unsecure-default-key.jks +0 -0
  29. package/webpack.config.js +31 -0
  30. package/www/data/encoded_site_logo.js +1 -0
  31. package/www/index.html +23 -493
  32. package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +193 -274
  33. package/config.xml +0 -27
  34. package/res/icon/android/icon.png +0 -0
  35. package/res/screen/android/splash-icon.png +0 -0
  36. package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
  37. package/www/js/routes/error.js +0 -5
  38. package/www/js/routes/init.js +0 -76
  39. package/www/js/utils/file_helpers.js +0 -108
  40. package/www/js/utils/offline_mode_helper.js +0 -625
package/.babelrc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "presets": ["@babel/preset-env"]
3
+ }
@@ -0,0 +1,47 @@
1
+ const { parseStringPromise, Builder } = require("xml2js");
2
+ const { join } = require("path");
3
+ const { readFileSync, writeFileSync } = require("fs");
4
+
5
+ (async () => {
6
+ try {
7
+ const androidManifest = join(
8
+ "android",
9
+ "app",
10
+ "src",
11
+ "main",
12
+ "AndroidManifest.xml"
13
+ );
14
+ const content = readFileSync(androidManifest);
15
+ const parsed = await parseStringPromise(content);
16
+
17
+ parsed.manifest["uses-permission"] = [
18
+ { $: { "android:name": "android.permission.READ_EXTERNAL_STORAGE" } },
19
+ { $: { "android:name": "android.permission.WRITE_EXTERNAL_STORAGE" } },
20
+ { $: { "android:name": "android.permission.INTERNET" } },
21
+ { $: { "android:name": "android.permission.CAMERA" } },
22
+ { $: { "android:name": "android.permission.ACCESS_COARSE_LOCATION" } },
23
+ { $: { "android:name": "android.permission.ACCESS_FINE_LOCATION" } },
24
+ ];
25
+ parsed.manifest["uses-feature"] = [
26
+ { $: { "android:name": "android.hardware.location.gps" } },
27
+ ];
28
+
29
+ parsed.manifest.application[0].$ = {
30
+ ...parsed.manifest.application[0].$,
31
+ "android:allowBackup": "false",
32
+ "android:fullBackupContent": "false",
33
+ "android:dataExtractionRules": "@xml/data_extraction_rules",
34
+ "android:networkSecurityConfig": "@xml/network_security_config",
35
+ "android:usesCleartextTraffic": "true",
36
+ };
37
+ const xmlBuilder = new Builder();
38
+ const newCfg = xmlBuilder.buildObject(parsed);
39
+ writeFileSync(androidManifest, newCfg);
40
+ } catch (error) {
41
+ console.log(
42
+ `Unable to modify the AndroidManifest.xml: ${
43
+ error.message ? error.message : "Unknown error"
44
+ }`
45
+ );
46
+ }
47
+ })();
@@ -0,0 +1,21 @@
1
+ const { join } = require("path");
2
+ const { readFileSync, writeFileSync } = require("fs");
3
+
4
+ console.log("Writing gradle config");
5
+ console.log("args", process.argv);
6
+ const args = process.argv.slice(2);
7
+ const appVersion = args[0].split("=")[1];
8
+
9
+ const gradleFile = join(__dirname, "..", "android", "app", "build.gradle");
10
+ const gradleContent = readFileSync(gradleFile, "utf8");
11
+
12
+ // generate versionCode from appVersion
13
+ const parts = appVersion.split(".");
14
+ const versionCode =
15
+ parseInt(parts[0]) * 1000000 + parseInt(parts[1]) * 1000 + parseInt(parts[2]);
16
+ let newGradleContent = gradleContent
17
+ .replace(/versionName "1.0"/, `versionName "${appVersion}"`)
18
+ .replace(/versionCode 1/, `versionCode ${versionCode}`);
19
+
20
+ console.log("newGradleContent", newGradleContent);
21
+ writeFileSync(gradleFile, newGradleContent, "utf8");
package/package.json CHANGED
@@ -1,25 +1,34 @@
1
1
  {
2
2
  "name": "@saltcorn/mobile-app",
3
3
  "displayName": "Saltcorn mobile app",
4
- "version": "1.1.0-beta.9",
5
- "description": "Apache Cordova application with @saltcorn/markup",
4
+ "version": "1.1.0",
5
+ "description": "Saltcorn mobile app for Android and iOS",
6
6
  "main": "index.js",
7
7
  "scripts": {
8
- "add-platform": "cordova platform add",
9
- "add-plugin": "cordova plugin add",
10
- "build-app": "cordova build --verbose",
8
+ "build": "webpack",
9
+ "add-platform": "npx cap add",
10
+ "modify-gradle-cfg": "node build_scripts/modify_gradle_cfg.js",
11
+ "modify-android-manifest": "node build_scripts/modify_android_manifest.js",
11
12
  "test": "echo NO TESTS"
12
13
  },
13
14
  "author": "Christian Hugo",
14
15
  "license": "MIT",
15
- "cordova": {
16
- "platforms": [
17
- "android"
18
- ]
19
- },
20
16
  "overrides": {
21
17
  "ansi-regex": "4.1.1"
22
18
  },
23
- "dependencies": {},
24
- "devDependencies": {}
19
+ "dependencies": {
20
+ "universal-router": "9.2.0",
21
+ "axios": "^1.7.7",
22
+ "jwt-decode": "4.0.0",
23
+ "i18next": "23.16.4",
24
+ "i18next-sprintf-postprocessor": "0.2.2"
25
+ },
26
+ "devDependencies": {
27
+ "xml2js": "0.6.0",
28
+ "@babel/core": "^7.26.0",
29
+ "@babel/preset-env": "^7.26.0",
30
+ "babel-loader": "^9.2.1",
31
+ "webpack": "^5.96.1",
32
+ "webpack-cli": "^5.1.4"
33
+ }
25
34
  }
package/src/.eslintrc ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "eslint:recommended",
3
+
4
+ "parserOptions": {
5
+ "ecmaVersion": 2021,
6
+ "sourceType": "module",
7
+
8
+ // typescript-eslint specific options
9
+ "warnOnUnsupportedTypeScriptVersion": true
10
+ },
11
+ "env": {
12
+ "browser": true
13
+ },
14
+ "ignorePatterns": ["dist/**"],
15
+ "rules": {
16
+ "no-unused-vars": "warn",
17
+ "no-case-declarations": "off",
18
+ "no-empty": "warn",
19
+ "no-fallthrough": "warn"
20
+ }
21
+ }
@@ -0,0 +1,43 @@
1
+ /*global saltcorn, splashConfig*/
2
+
3
+ import axios from "axios";
4
+
5
+ export async function apiCall({
6
+ method,
7
+ path,
8
+ params,
9
+ body,
10
+ responseType,
11
+ timeout,
12
+ additionalHeaders,
13
+ }) {
14
+ const config =
15
+ typeof saltcorn !== "undefined"
16
+ ? saltcorn.data.state.getState().mobileConfig
17
+ : splashConfig;
18
+ const serverPath = config.server_path;
19
+ const url = `${serverPath}${path}`;
20
+ const headers = {
21
+ "X-Requested-With": "XMLHttpRequest",
22
+ "X-Saltcorn-Client": "mobile-app",
23
+ ...(additionalHeaders || {}),
24
+ };
25
+ if (config.tenantAppName) headers["X-Saltcorn-App"] = config.tenantAppName;
26
+ const token = config.jwt;
27
+ if (token) headers.Authorization = `jwt ${token}`;
28
+ try {
29
+ const result = await axios({
30
+ url: url,
31
+ method,
32
+ params,
33
+ headers,
34
+ responseType: responseType ? responseType : "json",
35
+ data: body,
36
+ timeout: timeout ? timeout : 0,
37
+ });
38
+ return result;
39
+ } catch (error) {
40
+ error.message = `Unable to call ${method} ${url}:\n${error.message}`;
41
+ throw error;
42
+ }
43
+ }
@@ -0,0 +1,191 @@
1
+ /*global saltcorn*/
2
+
3
+ import { jwtDecode } from "jwt-decode";
4
+ import i18next from "i18next";
5
+ import { apiCall } from "./api";
6
+ import { router } from "../routing/index";
7
+ import { getLastOfflineSession, deleteOfflineData, sync } from "./offline_mode";
8
+ import { addRoute, replaceIframe } from "../helpers/navigation";
9
+ import { showAlerts } from "./common";
10
+
11
+ async function loginRequest({ email, password, isSignup, isPublic }) {
12
+ const opts = isPublic
13
+ ? {
14
+ method: "GET",
15
+ path: "/auth/login-with/jwt",
16
+ }
17
+ : isSignup
18
+ ? {
19
+ method: "POST",
20
+ path: "/auth/signup",
21
+ body: {
22
+ email,
23
+ password,
24
+ },
25
+ }
26
+ : {
27
+ method: "GET",
28
+ path: "/auth/login-with/jwt",
29
+ params: {
30
+ email,
31
+ password,
32
+ },
33
+ };
34
+ const response = await apiCall(opts);
35
+ return response.data;
36
+ }
37
+
38
+ export async function login({ email, password, entryPoint, isSignup }) {
39
+ const loginResult = await loginRequest({
40
+ email,
41
+ password,
42
+ isSignup,
43
+ });
44
+ if (typeof loginResult === "string") {
45
+ // use it as a token
46
+ const decodedJwt = jwtDecode(loginResult);
47
+ const config = saltcorn.data.state.getState().mobileConfig;
48
+ config.user = decodedJwt.user;
49
+ config.isPublicUser = false;
50
+ config.isOfflineMode = false;
51
+ await insertUser(config.user);
52
+ await setJwt(loginResult);
53
+ config.jwt = loginResult;
54
+ i18next.changeLanguage(config.user.language);
55
+ const alerts = [];
56
+ if (config.allowOfflineMode) {
57
+ const { offlineUser, hasOfflineData } =
58
+ (await getLastOfflineSession()) || {};
59
+ if (!offlineUser || offlineUser === config.user.email) {
60
+ await sync();
61
+ } else {
62
+ if (hasOfflineData)
63
+ alerts.push({
64
+ type: "warning",
65
+ msg: `'${offlineUser}' has not yet uploaded offline data.`,
66
+ });
67
+ else {
68
+ await deleteOfflineData(true);
69
+ await sync();
70
+ }
71
+ }
72
+ }
73
+ alerts.push({
74
+ type: "success",
75
+ msg: i18next.t("Welcome, %s!", {
76
+ postProcess: "sprintf",
77
+ sprintf: [config.user.email],
78
+ }),
79
+ });
80
+ addRoute({ route: entryPoint, query: undefined });
81
+ const page = await router.resolve({
82
+ pathname: entryPoint,
83
+ fullWrap: true,
84
+ alerts,
85
+ });
86
+ if (page.content) await replaceIframe(page.content, page.isFile);
87
+ } else if (loginResult?.alerts) {
88
+ showAlerts(loginResult?.alerts);
89
+ } else {
90
+ throw new Error("The login failed.");
91
+ }
92
+ }
93
+
94
+ export async function publicLogin(entryPoint) {
95
+ try {
96
+ const loginResult = await loginRequest({ isPublic: true });
97
+ if (typeof loginResult === "string") {
98
+ const config = saltcorn.data.state.getState().mobileConfig;
99
+ config.user = {
100
+ role_id: 100,
101
+ email: "public",
102
+ language: "en",
103
+ };
104
+ config.isPublicUser = true;
105
+ await setJwt(loginResult);
106
+ config.jwt = loginResult;
107
+ i18next.changeLanguage(config.user.language);
108
+ addRoute({
109
+ route: entryPoint,
110
+ query: undefined,
111
+ });
112
+ const page = await router.resolve({
113
+ pathname: entryPoint,
114
+ fullWrap: true,
115
+ alerts: [
116
+ {
117
+ type: "success",
118
+ msg: i18next.t("Welcome to %s!", {
119
+ postProcess: "sprintf",
120
+ sprintf: [
121
+ saltcorn.data.state.getState().getConfig("site_name") ||
122
+ "Saltcorn",
123
+ ],
124
+ }),
125
+ },
126
+ ],
127
+ });
128
+ if (page.content) await replaceIframe(page.content, page.isFile);
129
+ } else if (loginResult?.alerts) {
130
+ showAlerts(loginResult?.alerts);
131
+ } else {
132
+ throw new Error("The login failed.");
133
+ }
134
+ } catch (error) {
135
+ console.error(error);
136
+ showAlerts([
137
+ {
138
+ type: "error",
139
+ msg: error.message ? error.message : "An error occured.",
140
+ },
141
+ ]);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ export async function logout() {
147
+ try {
148
+ const config = saltcorn.data.state.getState().mobileConfig;
149
+ const page = await router.resolve({
150
+ pathname: "get/auth/logout",
151
+ entryView: config.entry_point,
152
+ versionTag: config.version_tag,
153
+ });
154
+ await replaceIframe(page.content);
155
+ } catch (error) {
156
+ showAlerts([
157
+ {
158
+ type: "error",
159
+ msg: error.message ? error.message : "An error occured.",
160
+ },
161
+ ]);
162
+ }
163
+ }
164
+
165
+ export async function insertUser({ id, email, role_id, language }) {
166
+ await saltcorn.data.db.insert(
167
+ "users",
168
+ { id, email, role_id, language },
169
+ { ignoreExisting: true }
170
+ );
171
+ }
172
+
173
+ export async function removeJwt() {
174
+ await saltcorn.data.db.deleteWhere("jwt_table");
175
+ }
176
+
177
+ export async function setJwt(jwt) {
178
+ await removeJwt();
179
+ await saltcorn.data.db.insert("jwt_table", { jwt: jwt });
180
+ }
181
+
182
+ export async function checkJWT(jwt) {
183
+ if (jwt && jwt !== "undefined") {
184
+ const response = await apiCall({
185
+ method: "GET",
186
+ path: "/auth/authenticated",
187
+ timeout: 10000,
188
+ });
189
+ return response.data.authenticated;
190
+ } else return false;
191
+ }
@@ -0,0 +1,189 @@
1
+ /*global saltcorn, $*/
2
+
3
+ import { apiCall } from "./api";
4
+ import { Camera, CameraResultType } from "@capacitor/camera";
5
+ import { Geolocation } from "@capacitor/geolocation";
6
+ import { ScreenOrientation } from "@capacitor/screen-orientation";
7
+
8
+ const orientationChangeListeners = new Set();
9
+
10
+ export function clearAlerts() {
11
+ const iframe = document.getElementById("content-iframe");
12
+ const alertsArea =
13
+ iframe.contentWindow.document.getElementById("toasts-area");
14
+ alertsArea.innerHTML = "";
15
+ }
16
+
17
+ export function showAlerts(alerts, toast = true) {
18
+ if (typeof saltcorn === "undefined") {
19
+ console.log("Not yet initalized.");
20
+ console.log(alerts);
21
+ } else {
22
+ const iframe = document.getElementById("content-iframe");
23
+ let area = iframe.contentWindow.document.getElementById(
24
+ toast ? "toasts-area" : "top-alert"
25
+ );
26
+ if (!area) {
27
+ const areaHtml = `<div class="container">
28
+ <div
29
+ id="toasts-area"
30
+ class="toast-container position-fixed bottom-0 start-50 p-0"
31
+ style="z-index: 9999;"
32
+ aria-live="polite"
33
+ aria-atomic="true">
34
+ </div>
35
+ </div>`;
36
+ iframe.contentWindow.document
37
+ .getElementById("page-inner-content")
38
+ .insertAdjacentHTML("beforeend", areaHtml);
39
+ area = iframe.contentWindow.document.getElementById(
40
+ toast ? "toasts-area" : "top-alert"
41
+ );
42
+ }
43
+ const successIds = [];
44
+ area.innerHTML = "";
45
+ for (const { type, msg } of alerts) {
46
+ if (toast) {
47
+ const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
48
+ area.innerHTML += saltcorn.markup.toast(type, msg, rndid);
49
+ if (type === "success") successIds.push(rndid);
50
+ } else area.innerHTML += saltcorn.markup.alert(type, msg);
51
+ }
52
+ if (successIds.length > 0) {
53
+ setTimeout(() => {
54
+ for (const id of successIds) {
55
+ const toastEl = iframe.contentWindow.document.getElementById(id);
56
+ if (toastEl) $(toastEl).removeClass("show");
57
+ }
58
+ }, 5000);
59
+ }
60
+ }
61
+ return true;
62
+ }
63
+
64
+ export function showLoadSpinner() {
65
+ const iframe = document.getElementById("content-iframe");
66
+ if (iframe) iframe.contentWindow.showLoadSpinner();
67
+ }
68
+
69
+ export function removeLoadSpinner() {
70
+ const iframe = document.getElementById("content-iframe");
71
+ if (iframe) iframe.contentWindow.removeLoadSpinner();
72
+ }
73
+
74
+ export function clearTopAlerts() {
75
+ const iframe = document.getElementById("content-iframe");
76
+ const area = iframe.contentWindow.document.getElementById("alerts-area");
77
+ if (area) area.innerHTML = "";
78
+ const topAlert = iframe.contentWindow.document.getElementById("top-alert");
79
+ if (topAlert) topAlert.innerHTML = "";
80
+ }
81
+
82
+ export function errorAlert(error) {
83
+ showAlerts([
84
+ {
85
+ type: "error",
86
+ msg: error.message ? error.message : "An error occured.",
87
+ },
88
+ ]);
89
+ console.error(error);
90
+ }
91
+
92
+ // TODO combine with loadEncodedFile
93
+ export async function loadFileAsText(fileId) {
94
+ try {
95
+ const response = await apiCall({
96
+ method: "GET",
97
+ path: `/files/download/${fileId}`,
98
+ responseType: "blob",
99
+ });
100
+ return new Promise((resolve, reject) => {
101
+ const reader = new FileReader();
102
+ reader.onloadend = () => {
103
+ return resolve(reader.result);
104
+ };
105
+ reader.onerror = (error) => {
106
+ return reject(error);
107
+ };
108
+ reader.readAsText(response.data);
109
+ });
110
+ } catch (error) {
111
+ if (
112
+ !showAlerts([
113
+ {
114
+ type: "error",
115
+ msg: error.message ? error.message : "An error occured.",
116
+ },
117
+ ])
118
+ );
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ export async function loadEncodedFile(fileId) {
124
+ try {
125
+ const response = await apiCall({
126
+ method: "GET",
127
+ path: `/files/download/${fileId}`,
128
+ responseType: "blob",
129
+ });
130
+ return new Promise((resolve, reject) => {
131
+ const reader = new FileReader();
132
+ reader.onloadend = () => {
133
+ return resolve(reader.result);
134
+ };
135
+ reader.onerror = (error) => {
136
+ return reject(error);
137
+ };
138
+ reader.readAsDataURL(response.data);
139
+ });
140
+ } catch (error) {
141
+ showAlerts([
142
+ {
143
+ type: "error",
144
+ msg: error.message ? error.message : "An error occured.",
145
+ },
146
+ ]);
147
+ }
148
+ }
149
+
150
+ export async function takePhoto() {
151
+ try {
152
+ const image = await Camera.getPhoto({
153
+ quality: 90,
154
+ allowEditing: false,
155
+ resultType: CameraResultType.Uri,
156
+ });
157
+ return image.path;
158
+ } catch (error) {
159
+ showAlerts([
160
+ {
161
+ type: "error",
162
+ msg: error.message ? error.message : "An error occured.",
163
+ },
164
+ ]);
165
+ return null;
166
+ }
167
+ }
168
+
169
+ export async function getGeolocation(successCb, errorCb) {
170
+ try {
171
+ const coordinates = await Geolocation.getCurrentPosition();
172
+ if (successCb) successCb(coordinates);
173
+ return coordinates;
174
+ } catch (error) {
175
+ if (errorCb) errorCb(error);
176
+ return null;
177
+ }
178
+ }
179
+
180
+ export function registerScreenOrientationListener(name, listener) {
181
+ if (!orientationChangeListeners.has(name)) {
182
+ orientationChangeListeners.add(name, listener);
183
+ ScreenOrientation.addListener("screenOrientationChange", listener);
184
+ } else console.warn(`Listener with name ${name} already registered.`);
185
+ }
186
+
187
+ export async function getScreenOrientation() {
188
+ return await ScreenOrientation.orientation();
189
+ }
@@ -1,13 +1,16 @@
1
- /*global readJSON, cordova, fileExists, writeJSON, saltcorn*/
1
+ /*global saltcorn */
2
2
 
3
3
  const historyFile = "update_history";
4
4
  const jwtTableName = "jwt_table";
5
5
 
6
+ import { readFile, fileExists, writeJSON } from "./file_system";
7
+ import { Directory } from "@capacitor/filesystem";
8
+
6
9
  /**
7
10
  * drop tables that are no longer in the 'tables.json' file
8
11
  * the server db uses a serial (with postgres), so checking ids should suffice
9
12
  */
10
- async function dropDeletedTables(incomingTables) {
13
+ export async function dropDeletedTables(incomingTables) {
11
14
  const existingTables = await saltcorn.data.models.Table.find();
12
15
  for (const table of existingTables) {
13
16
  if (
@@ -27,7 +30,7 @@ async function dropDeletedTables(incomingTables) {
27
30
  * @param {*} rows
28
31
  * @returns
29
32
  */
30
- async function safeRows(table, rows) {
33
+ export async function safeRows(table, rows) {
31
34
  const existingFields = (
32
35
  await saltcorn.data.db.query(
33
36
  `PRAGMA table_info('${saltcorn.data.db.sqlsanitize(table)}')`
@@ -45,7 +48,7 @@ async function safeRows(table, rows) {
45
48
  });
46
49
  }
47
50
 
48
- async function updateScTables(tablesJSON, skipScPlugins = true) {
51
+ export async function updateScTables(tablesJSON, skipScPlugins = true) {
49
52
  await saltcorn.data.db.query("PRAGMA foreign_keys = OFF;");
50
53
  for (const { table, rows } of tablesJSON.sc_tables) {
51
54
  if (skipScPlugins && table === "_sc_plugins") continue;
@@ -56,7 +59,7 @@ async function updateScTables(tablesJSON, skipScPlugins = true) {
56
59
  await saltcorn.data.db.query("PRAGMA foreign_keys = ON;");
57
60
  }
58
61
 
59
- async function updateScPlugins(tablesJSON) {
62
+ export async function updateScPlugins(tablesJSON) {
60
63
  const { table, rows } = tablesJSON.sc_tables.find(
61
64
  ({ table }) => table === "_sc_plugins"
62
65
  );
@@ -66,7 +69,7 @@ async function updateScPlugins(tablesJSON) {
66
69
  }
67
70
  }
68
71
 
69
- async function updateUserDefinedTables() {
72
+ export async function updateUserDefinedTables() {
70
73
  const existingTables = await saltcorn.data.db.listUserDefinedTables();
71
74
  const tables = await saltcorn.data.models.Table.find();
72
75
  for (const table of tables) {
@@ -92,7 +95,7 @@ async function updateUserDefinedTables() {
92
95
  }
93
96
  }
94
97
 
95
- async function createSyncInfoTables(synchTbls) {
98
+ export async function createSyncInfoTables(synchTbls) {
96
99
  const infoTbls = (await saltcorn.data.db.listTables()).filter(({ name }) => {
97
100
  name.endsWith("_sync_info");
98
101
  });
@@ -139,11 +142,8 @@ async function createSyncInfoTables(synchTbls) {
139
142
  }
140
143
  }
141
144
 
142
- async function tablesUptodate(createdAt, historyFile) {
143
- const { updated_at } = await readJSON(
144
- historyFile,
145
- cordova.file.dataDirectory
146
- );
145
+ export async function tablesUptodate(createdAt, historyFile) {
146
+ const { updated_at } = await readFile(historyFile, Directory.Data);
147
147
  if (!updated_at) {
148
148
  console.log("No updated_at in history file");
149
149
  return false;
@@ -155,52 +155,30 @@ async function tablesUptodate(createdAt, historyFile) {
155
155
  * Do a table update when the history file doesn't exist or is older than createdAt
156
156
  * @param {number} createdAt UTC Date number when the tables.json file was created on the server
157
157
  */
158
- async function dbUpdateNeeded(createdAt) {
158
+ export async function dbUpdateNeeded(createdAt) {
159
159
  return (
160
- !(await fileExists(`${cordova.file.dataDirectory}${historyFile}`)) ||
160
+ !(await fileExists(historyFile, Directory.Data)) ||
161
161
  !(await tablesUptodate(createdAt, historyFile))
162
162
  );
163
163
  }
164
164
 
165
- async function updateDb(tablesJSON) {
165
+ export async function updateDb(tablesJSON) {
166
166
  await updateScTables(tablesJSON);
167
167
  await saltcorn.data.state.getState().refresh_tables();
168
168
  await updateUserDefinedTables();
169
- await writeJSON(historyFile, cordova.file.dataDirectory, {
169
+ await writeJSON(historyFile, Directory.Data, {
170
170
  updated_at: new Date().valueOf(),
171
171
  });
172
172
  }
173
173
 
174
- async function getTableIds(tableNames) {
174
+ export async function getTableIds(tableNames) {
175
175
  return (await saltcorn.data.models.Table.find())
176
176
  .filter((table) => tableNames.indexOf(table.name) > -1)
177
177
  .map((table) => table.id);
178
178
  }
179
179
 
180
- async function createJwtTable() {
180
+ export async function createJwtTable() {
181
181
  await saltcorn.data.db.query(`CREATE TABLE IF NOT EXISTS ${jwtTableName} (
182
182
  jwt VARCHAR(500)
183
183
  )`);
184
184
  }
185
-
186
- async function getJwt() {
187
- const rows = await saltcorn.data.db.select(jwtTableName);
188
- return rows?.length > 0 ? rows[0].jwt : null;
189
- }
190
-
191
- async function removeJwt() {
192
- await saltcorn.data.db.deleteWhere(jwtTableName);
193
- }
194
-
195
- async function setJwt(jwt) {
196
- await removeJwt();
197
- await saltcorn.data.db.insert(jwtTableName, { jwt: jwt });
198
- }
199
-
200
- async function insertUser({ id, email, role_id, language }) {
201
- await saltcorn.data.db.insert(
202
- "users",
203
- { id, email, role_id, language },
204
- { ignoreExisting: true }
205
- );
206
- }