@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.
- package/.babelrc +3 -0
- package/build_scripts/modify_android_manifest.js +47 -0
- package/build_scripts/modify_gradle_cfg.js +21 -0
- package/package.json +21 -12
- package/src/.eslintrc +21 -0
- package/src/helpers/api.js +43 -0
- package/src/helpers/auth.js +191 -0
- package/src/helpers/common.js +189 -0
- package/{www/js/utils/table_utils.js → src/helpers/db_schema.js} +18 -40
- package/src/helpers/file_system.js +102 -0
- package/{www/js/utils/global_utils.js → src/helpers/navigation.js} +189 -332
- package/src/helpers/offline_mode.js +645 -0
- package/src/index.js +20 -0
- package/src/init.js +424 -0
- package/src/routing/index.js +98 -0
- package/{www/js → src/routing}/mocks/request.js +5 -5
- package/{www/js → src/routing}/mocks/response.js +1 -1
- package/{www/js → src/routing}/routes/api.js +10 -15
- package/{www/js → src/routing}/routes/auth.js +12 -6
- package/{www/js → src/routing}/routes/delete.js +9 -6
- package/{www/js → src/routing}/routes/edit.js +9 -6
- package/src/routing/routes/error.js +6 -0
- package/{www/js → src/routing}/routes/fields.js +7 -2
- package/{www/js → src/routing}/routes/page.js +15 -10
- package/{www/js → src/routing}/routes/sync.js +17 -8
- package/{www/js → src/routing}/routes/view.js +20 -15
- package/{www/js/routes/common.js → src/routing/utils.js} +18 -13
- package/unsecure-default-key.jks +0 -0
- package/webpack.config.js +31 -0
- package/www/data/encoded_site_logo.js +1 -0
- package/www/index.html +23 -493
- package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +193 -274
- package/config.xml +0 -27
- package/res/icon/android/icon.png +0 -0
- package/res/screen/android/splash-icon.png +0 -0
- package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
- package/www/js/routes/error.js +0 -5
- package/www/js/routes/init.js +0 -76
- package/www/js/utils/file_helpers.js +0 -108
- package/www/js/utils/offline_mode_helper.js +0 -625
package/src/init.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/*global saltcorn */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
startOfflineMode,
|
|
5
|
+
networkChangeCallback,
|
|
6
|
+
sync,
|
|
7
|
+
getLastOfflineSession,
|
|
8
|
+
} from "./helpers/offline_mode.js";
|
|
9
|
+
import {
|
|
10
|
+
updateScPlugins,
|
|
11
|
+
createSyncInfoTables,
|
|
12
|
+
dbUpdateNeeded,
|
|
13
|
+
updateDb,
|
|
14
|
+
getTableIds,
|
|
15
|
+
createJwtTable,
|
|
16
|
+
} from "./helpers/db_schema.js";
|
|
17
|
+
import { publicLogin, checkJWT } from "./helpers/auth.js";
|
|
18
|
+
import { router } from "./routing/index.js";
|
|
19
|
+
import {
|
|
20
|
+
replaceIframe,
|
|
21
|
+
clearHistory,
|
|
22
|
+
gotoEntryView,
|
|
23
|
+
addRoute,
|
|
24
|
+
} from "./helpers/navigation.js";
|
|
25
|
+
|
|
26
|
+
import i18next from "i18next";
|
|
27
|
+
import i18nextSprintfPostProcessor from "i18next-sprintf-postprocessor";
|
|
28
|
+
import { jwtDecode } from "jwt-decode";
|
|
29
|
+
|
|
30
|
+
import { Network } from "@capacitor/network";
|
|
31
|
+
|
|
32
|
+
async function addScript(scriptObj) {
|
|
33
|
+
let waited = 0;
|
|
34
|
+
const maxWait = 3000;
|
|
35
|
+
|
|
36
|
+
const moduleAvailable = () =>
|
|
37
|
+
window.saltcorn && window.saltcorn[scriptObj.name];
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
let script = document.createElement("script");
|
|
41
|
+
document.head.appendChild(script);
|
|
42
|
+
|
|
43
|
+
const waitForModule = () => {
|
|
44
|
+
waited += 100;
|
|
45
|
+
if (waited >= maxWait)
|
|
46
|
+
return reject(`unable to load '${scriptObj.name}'`);
|
|
47
|
+
console.log("waiting for " + scriptObj.name);
|
|
48
|
+
setTimeout(function () {
|
|
49
|
+
if (moduleAvailable()) return resolve();
|
|
50
|
+
else waitForModule();
|
|
51
|
+
}, 100);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
script.onload = () => {
|
|
55
|
+
if (!scriptObj.name || moduleAvailable()) return resolve();
|
|
56
|
+
waitForModule();
|
|
57
|
+
};
|
|
58
|
+
script.src = scriptObj.src;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function loadPlugin(plugin) {
|
|
63
|
+
await addScript({
|
|
64
|
+
src: `js/bundle/${plugin.name}.bundle.js`,
|
|
65
|
+
name: plugin.name,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function loadPlugins(state) {
|
|
70
|
+
const plugins = (await saltcorn.data.models.Plugin.find()).filter(
|
|
71
|
+
(plugin) => !["base", "sbadmin2"].includes(plugin.name)
|
|
72
|
+
);
|
|
73
|
+
for (const plugin of plugins) {
|
|
74
|
+
await loadPlugin(plugin);
|
|
75
|
+
state.registerPlugin(
|
|
76
|
+
plugin.name,
|
|
77
|
+
saltcorn[plugin.name],
|
|
78
|
+
plugin.configuration
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return plugins;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* add <script> tags dynamically
|
|
86
|
+
*/
|
|
87
|
+
async function addScripts(version_tag) {
|
|
88
|
+
const scripts = [
|
|
89
|
+
{ src: `static_assets/${version_tag}/jquery-3.6.0.min.js` },
|
|
90
|
+
{ src: "js/bundle/common_chunks.bundle.js" },
|
|
91
|
+
{ src: "js/bundle/markup.bundle.js", name: "markup" },
|
|
92
|
+
{ src: "js/bundle/data.bundle.js", name: "data" },
|
|
93
|
+
{ src: "js/bundle/base_plugin.bundle.js", name: "base_plugin" },
|
|
94
|
+
{ src: "js/bundle/sbadmin2.bundle.js", name: "sbadmin2" },
|
|
95
|
+
];
|
|
96
|
+
for (const script of scripts) {
|
|
97
|
+
await addScript(script);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const prepareHeader = (header) => {
|
|
102
|
+
let result = Object.assign({}, header);
|
|
103
|
+
const replacer = (key) => {
|
|
104
|
+
const value = header[key];
|
|
105
|
+
if (value?.startsWith("/plugins") || value?.startsWith("plugins"))
|
|
106
|
+
result[key] = value.replace(/^\/?plugins/, "sc_plugins");
|
|
107
|
+
};
|
|
108
|
+
replacer("script");
|
|
109
|
+
replacer("css");
|
|
110
|
+
return result;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
A plugin exports headers either as array, as key values object, or
|
|
115
|
+
as a function with a configuration parameter that returns an Array.
|
|
116
|
+
*/
|
|
117
|
+
const collectPluginHeaders = (pluginRows) => {
|
|
118
|
+
const config = saltcorn.data.state.getState().mobileConfig;
|
|
119
|
+
config.pluginHeaders = [];
|
|
120
|
+
for (const row of pluginRows) {
|
|
121
|
+
const pluginHeaders = saltcorn[row.name].headers;
|
|
122
|
+
if (pluginHeaders) {
|
|
123
|
+
if (Array.isArray(pluginHeaders))
|
|
124
|
+
for (const header of pluginHeaders) {
|
|
125
|
+
config.pluginHeaders.push(prepareHeader(header));
|
|
126
|
+
}
|
|
127
|
+
else if (typeof pluginHeaders === "function") {
|
|
128
|
+
const headerResult = pluginHeaders(row.configuration || {});
|
|
129
|
+
if (Array.isArray(headerResult)) {
|
|
130
|
+
for (const header of headerResult)
|
|
131
|
+
config.pluginHeaders.push(prepareHeader(header));
|
|
132
|
+
}
|
|
133
|
+
} else
|
|
134
|
+
for (const value of Object.values(pluginHeaders)) {
|
|
135
|
+
config.pluginHeaders.push(prepareHeader(value));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const getJwt = async () => {
|
|
142
|
+
const rows = await saltcorn.data.db.select("jwt_table");
|
|
143
|
+
return rows?.length > 0 ? rows[0].jwt : null;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const initJwt = async () => {
|
|
147
|
+
if (!(await saltcorn.data.db.tableExists("jwt_table"))) {
|
|
148
|
+
await createJwtTable();
|
|
149
|
+
} else {
|
|
150
|
+
const jwt = await getJwt();
|
|
151
|
+
if (jwt) {
|
|
152
|
+
const state = saltcorn.data.state.getState();
|
|
153
|
+
state.mobileConfig.jwt = jwt;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const initI18Next = async (allLanguages) => {
|
|
159
|
+
await i18next.use(i18nextSprintfPostProcessor).init({
|
|
160
|
+
lng: "en",
|
|
161
|
+
allLanguages,
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const showErrorPage = async (error) => {
|
|
166
|
+
const state = saltcorn.data.state.getState();
|
|
167
|
+
state.mobileConfig.inErrorState = true;
|
|
168
|
+
const page = await router.resolve({
|
|
169
|
+
pathname: "get/error_page",
|
|
170
|
+
fullWrap: true,
|
|
171
|
+
alerts: [
|
|
172
|
+
{
|
|
173
|
+
type: "error",
|
|
174
|
+
msg: error.message ? error.message : "An error occured.",
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
await replaceIframe(page.content);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// the app comes back from background
|
|
182
|
+
const onResume = async () => {
|
|
183
|
+
if (typeof saltcorn === "undefined") return;
|
|
184
|
+
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
185
|
+
if (mobileConfig?.allowOfflineMode) {
|
|
186
|
+
const netStatus = await Network.getStatus();
|
|
187
|
+
mobileConfig.networkState = netStatus.connectionType;
|
|
188
|
+
if (
|
|
189
|
+
mobileConfig.networkState === "none" &&
|
|
190
|
+
!mobileConfig.isOfflineMode &&
|
|
191
|
+
mobileConfig.jwt
|
|
192
|
+
) {
|
|
193
|
+
try {
|
|
194
|
+
await startOfflineMode();
|
|
195
|
+
clearHistory();
|
|
196
|
+
if (mobileConfig.user?.id) await gotoEntryView();
|
|
197
|
+
else {
|
|
198
|
+
const decodedJwt = jwtDecode(mobileConfig.jwt);
|
|
199
|
+
mobileConfig.user = decodedJwt.user;
|
|
200
|
+
mobileConfig.isPublicUser = false;
|
|
201
|
+
}
|
|
202
|
+
addRoute({ route: mobileConfig.entry_point, query: undefined });
|
|
203
|
+
const page = await router.resolve({
|
|
204
|
+
pathname: mobileConfig.entry_point,
|
|
205
|
+
fullWrap: true,
|
|
206
|
+
alerts: [],
|
|
207
|
+
});
|
|
208
|
+
} catch (error) {
|
|
209
|
+
await showErrorPage(error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const isPublicJwt = (jwt) => {
|
|
216
|
+
try {
|
|
217
|
+
if (!jwt) return false;
|
|
218
|
+
const decoded = jwtDecode(jwt);
|
|
219
|
+
return decoded.sub === "public";
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.log(
|
|
222
|
+
`Unable to inspect '${jwt}': ${
|
|
223
|
+
error.message ? error.message : "Unknown error"
|
|
224
|
+
}`
|
|
225
|
+
);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const isPublicEntryPoint = async (entryPoint) => {
|
|
231
|
+
try {
|
|
232
|
+
const tokens = entryPoint.split("/");
|
|
233
|
+
if (tokens.length < 3) throw new Error("The format is incorrect");
|
|
234
|
+
const name = tokens[tokens.length - 1];
|
|
235
|
+
const entryObj =
|
|
236
|
+
tokens[tokens.length - 2] === "view"
|
|
237
|
+
? saltcorn.data.models.View.findOne({ name: name })
|
|
238
|
+
: saltcorn.data.models.Page.findOne({ name: name });
|
|
239
|
+
if (!entryObj) throw new Error(`The object '${name}' does not exist`);
|
|
240
|
+
else return entryObj.min_role === 100;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.log(
|
|
243
|
+
`Unable to inspect '${entryPoint}': ${
|
|
244
|
+
error.message ? error.message : "Unknown error"
|
|
245
|
+
}`
|
|
246
|
+
);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const showLogin = async (alerts) => {
|
|
252
|
+
const page = await router.resolve({
|
|
253
|
+
pathname: "get/auth/login",
|
|
254
|
+
alerts,
|
|
255
|
+
});
|
|
256
|
+
await replaceIframe(page.content);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const takeLastLocation = () => {
|
|
260
|
+
let result = null;
|
|
261
|
+
const lastLocation = localStorage.getItem("lastLocation");
|
|
262
|
+
localStorage.removeItem("lastLocation");
|
|
263
|
+
if (lastLocation) {
|
|
264
|
+
try {
|
|
265
|
+
result = JSON.parse(lastLocation);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.log(
|
|
268
|
+
`Unable to parse the last location: ${
|
|
269
|
+
error.message ? error.message : "Unknown error"
|
|
270
|
+
}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// device is ready
|
|
278
|
+
export async function init({
|
|
279
|
+
mobileConfig,
|
|
280
|
+
tablesSchema,
|
|
281
|
+
schemaCreatedAt,
|
|
282
|
+
translations,
|
|
283
|
+
siteLogo,
|
|
284
|
+
}) {
|
|
285
|
+
try {
|
|
286
|
+
const lastLocation = takeLastLocation();
|
|
287
|
+
document.addEventListener("resume", onResume, false);
|
|
288
|
+
const { created_at } = schemaCreatedAt;
|
|
289
|
+
await addScripts(mobileConfig.version_tag);
|
|
290
|
+
saltcorn.data.db.connectObj.version_tag = mobileConfig.version_tag;
|
|
291
|
+
|
|
292
|
+
await saltcorn.data.db.init();
|
|
293
|
+
const updateNeeded = await dbUpdateNeeded(created_at);
|
|
294
|
+
if (updateNeeded) {
|
|
295
|
+
// update '_sc_plugins' first because of loadPlugins()
|
|
296
|
+
await updateScPlugins(tablesSchema);
|
|
297
|
+
}
|
|
298
|
+
const state = saltcorn.data.state.getState();
|
|
299
|
+
state.mobileConfig = mobileConfig;
|
|
300
|
+
state.mobileConfig.user = {};
|
|
301
|
+
state.registerPlugin("base", saltcorn.base_plugin);
|
|
302
|
+
state.registerPlugin("sbadmin2", saltcorn.sbadmin2);
|
|
303
|
+
collectPluginHeaders(await loadPlugins(state));
|
|
304
|
+
if (updateNeeded) await updateDb(tablesSchema);
|
|
305
|
+
await createSyncInfoTables(mobileConfig.synchedTables);
|
|
306
|
+
await initJwt();
|
|
307
|
+
await state.refresh_tables();
|
|
308
|
+
await state.refresh_views();
|
|
309
|
+
await state.refresh_pages();
|
|
310
|
+
await state.refresh_page_groups();
|
|
311
|
+
await state.refresh_triggers();
|
|
312
|
+
state.mobileConfig.localTableIds = await getTableIds(
|
|
313
|
+
mobileConfig.localUserTables
|
|
314
|
+
);
|
|
315
|
+
await state.setConfig("base_url", mobileConfig.server_path);
|
|
316
|
+
const entryPoint = mobileConfig.entry_point;
|
|
317
|
+
await initI18Next(translations);
|
|
318
|
+
state.mobileConfig.encodedSiteLogo = siteLogo;
|
|
319
|
+
|
|
320
|
+
state.mobileConfig.networkState = await Network.getStatus();
|
|
321
|
+
Network.addListener("networkStatusChange", networkChangeCallback);
|
|
322
|
+
const networkDisabled = state.mobileConfig.networkState === "none";
|
|
323
|
+
const jwt = state.mobileConfig.jwt;
|
|
324
|
+
const alerts = [];
|
|
325
|
+
if ((networkDisabled && jwt) || (await checkJWT(jwt))) {
|
|
326
|
+
const mobileConfig = state.mobileConfig;
|
|
327
|
+
const decodedJwt = jwtDecode(mobileConfig.jwt);
|
|
328
|
+
mobileConfig.user = decodedJwt.user;
|
|
329
|
+
mobileConfig.isPublicUser = false;
|
|
330
|
+
await i18next.changeLanguage(mobileConfig.user.language);
|
|
331
|
+
if (mobileConfig.allowOfflineMode) {
|
|
332
|
+
const { offlineUser } = (await getLastOfflineSession()) || {};
|
|
333
|
+
if (networkDisabled) {
|
|
334
|
+
if (offlineUser && offlineUser !== mobileConfig.user.email)
|
|
335
|
+
throw new Error(
|
|
336
|
+
`The offline mode is not available, '${offlineUser}' has not yet uploaded offline data.`
|
|
337
|
+
);
|
|
338
|
+
else
|
|
339
|
+
try {
|
|
340
|
+
await startOfflineMode();
|
|
341
|
+
} catch (error) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`Neither an internet connection nor the offline mode are available: ${
|
|
344
|
+
error.message ? error.message : "Unknown error"
|
|
345
|
+
}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
} else if (offlineUser) {
|
|
349
|
+
if (offlineUser === mobileConfig.user.email) {
|
|
350
|
+
await sync();
|
|
351
|
+
alerts.push({
|
|
352
|
+
type: "info",
|
|
353
|
+
msg: "Synchronized your offline data.",
|
|
354
|
+
});
|
|
355
|
+
} else
|
|
356
|
+
alerts.push({
|
|
357
|
+
type: "warning",
|
|
358
|
+
msg: `'${offlineUser}' has not yet uploaded offline data.`,
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
await sync();
|
|
362
|
+
alerts.push({
|
|
363
|
+
type: "info",
|
|
364
|
+
msg: "Synchronized your offline data.",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
let page = null;
|
|
369
|
+
if (!lastLocation) {
|
|
370
|
+
addRoute({ route: entryPoint, query: undefined });
|
|
371
|
+
page = await router.resolve({
|
|
372
|
+
pathname: entryPoint,
|
|
373
|
+
fullWrap: true,
|
|
374
|
+
alerts,
|
|
375
|
+
});
|
|
376
|
+
} else {
|
|
377
|
+
addRoute({
|
|
378
|
+
route: lastLocation.route,
|
|
379
|
+
query: lastLocation.query,
|
|
380
|
+
});
|
|
381
|
+
page = await router.resolve({
|
|
382
|
+
pathname: lastLocation.route,
|
|
383
|
+
query: lastLocation.query,
|
|
384
|
+
fullWrap: true,
|
|
385
|
+
alerts,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (page.content) await replaceIframe(page.content, page.isFile);
|
|
389
|
+
} else if (isPublicJwt(jwt)) {
|
|
390
|
+
const config = state.mobileConfig;
|
|
391
|
+
config.user = { role_id: 100, email: "public", language: "en" };
|
|
392
|
+
config.isPublicUser = true;
|
|
393
|
+
i18next.changeLanguage(config.user.language);
|
|
394
|
+
addRoute({ route: entryPoint, query: undefined });
|
|
395
|
+
const page = await router.resolve({
|
|
396
|
+
pathname: entryPoint,
|
|
397
|
+
fullWrap: true,
|
|
398
|
+
alerts,
|
|
399
|
+
});
|
|
400
|
+
if (page.content) await replaceIframe(page.content, page.isFile);
|
|
401
|
+
} else if (
|
|
402
|
+
(await isPublicEntryPoint(entryPoint)) &&
|
|
403
|
+
state.mobileConfig.autoPublicLogin
|
|
404
|
+
) {
|
|
405
|
+
if (networkDisabled)
|
|
406
|
+
throw new Error(
|
|
407
|
+
"No internet connection or previous login is available. " +
|
|
408
|
+
"Please go online and reload, the public login is not yet supported."
|
|
409
|
+
);
|
|
410
|
+
await publicLogin(entryPoint);
|
|
411
|
+
} else await showLogin(alerts);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (typeof saltcorn === "undefined" || typeof router === "undefined") {
|
|
414
|
+
const msg = `An error occured: ${
|
|
415
|
+
error.message ? error.message : "Unknown error"
|
|
416
|
+
}`;
|
|
417
|
+
console.log(msg);
|
|
418
|
+
alert(msg);
|
|
419
|
+
} else {
|
|
420
|
+
if (error.httpCode === 401) await showLogin([]);
|
|
421
|
+
else await showErrorPage(error);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import UniversalRouter from "universal-router";
|
|
2
|
+
|
|
3
|
+
import { updateTableRow, insertTableRow } from "./routes/api";
|
|
4
|
+
import { getLoginView, logoutAction, getSignupView } from "./routes/auth";
|
|
5
|
+
import { deleteRows } from "./routes/delete";
|
|
6
|
+
import { postToggleField } from "./routes/edit";
|
|
7
|
+
import { getErrorView } from "./routes/error";
|
|
8
|
+
import { postShowCalculated } from "./routes/fields";
|
|
9
|
+
import { postPageAction, getPage } from "./routes/page";
|
|
10
|
+
import {
|
|
11
|
+
getSyncSettingsView,
|
|
12
|
+
getAskDeleteOfflineData,
|
|
13
|
+
getAskUploadNotEnded,
|
|
14
|
+
} from "./routes/sync";
|
|
15
|
+
|
|
16
|
+
import { postView, postViewRoute, getView } from "./routes/view";
|
|
17
|
+
|
|
18
|
+
const routes = [
|
|
19
|
+
// api
|
|
20
|
+
{
|
|
21
|
+
path: "post/api/:tableName/:id",
|
|
22
|
+
action: updateTableRow,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
path: "post/api/:tableName/",
|
|
26
|
+
action: insertTableRow,
|
|
27
|
+
},
|
|
28
|
+
// auth
|
|
29
|
+
{
|
|
30
|
+
path: "get/auth/login",
|
|
31
|
+
action: getLoginView,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
path: "get/auth/logout",
|
|
35
|
+
action: logoutAction,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: "get/auth/signup",
|
|
39
|
+
action: getSignupView,
|
|
40
|
+
},
|
|
41
|
+
// delete
|
|
42
|
+
{
|
|
43
|
+
path: "post/delete/:name/:id",
|
|
44
|
+
action: deleteRows,
|
|
45
|
+
},
|
|
46
|
+
// edit
|
|
47
|
+
{
|
|
48
|
+
path: "post/edit/toggle/:name/:id/:field_name",
|
|
49
|
+
action: postToggleField,
|
|
50
|
+
},
|
|
51
|
+
// error
|
|
52
|
+
{
|
|
53
|
+
path: "get/error_page",
|
|
54
|
+
action: getErrorView,
|
|
55
|
+
},
|
|
56
|
+
// field
|
|
57
|
+
{
|
|
58
|
+
path: "post/field/show-calculated/:tableName/:fieldName/:fieldview",
|
|
59
|
+
action: postShowCalculated,
|
|
60
|
+
},
|
|
61
|
+
// page
|
|
62
|
+
{
|
|
63
|
+
path: "post/page/:page_name/action/:rndid",
|
|
64
|
+
action: postPageAction,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
path: "get/page/:page_name",
|
|
68
|
+
action: getPage,
|
|
69
|
+
},
|
|
70
|
+
// sync
|
|
71
|
+
{
|
|
72
|
+
path: "get/sync/sync_settings",
|
|
73
|
+
action: getSyncSettingsView,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
path: "get/sync/ask_upload_not_ended",
|
|
77
|
+
action: getAskUploadNotEnded,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
path: "get/sync/ask_delete_offline_data",
|
|
81
|
+
action: getAskDeleteOfflineData,
|
|
82
|
+
},
|
|
83
|
+
// view
|
|
84
|
+
{
|
|
85
|
+
path: "post/view/:viewname",
|
|
86
|
+
action: postView,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
path: "post/view/:viewname/:route",
|
|
90
|
+
action: postViewRoute,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
path: "get/view/:viewname",
|
|
94
|
+
action: getView,
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
export const router = new UniversalRouter(routes);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/*global
|
|
1
|
+
/*global saltcorn, */
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import i18next from "i18next";
|
|
4
|
+
|
|
5
|
+
export function MobileRequest({
|
|
4
6
|
xhr = false,
|
|
5
7
|
files = undefined,
|
|
6
8
|
query = undefined,
|
|
@@ -8,8 +10,6 @@ function MobileRequest({
|
|
|
8
10
|
refererRoute = undefined,
|
|
9
11
|
} = {}) {
|
|
10
12
|
const cfg = saltcorn.data.state.getState().mobileConfig;
|
|
11
|
-
const roleId = cfg.role_id ? cfg.role_id : 100;
|
|
12
|
-
const userId = cfg.user_id ? cfg.user_id : undefined;
|
|
13
13
|
const flashMessages = [];
|
|
14
14
|
const refererPath = refererRoute ? refererRoute.route : undefined;
|
|
15
15
|
const referQuery =
|
|
@@ -33,7 +33,7 @@ function MobileRequest({
|
|
|
33
33
|
},
|
|
34
34
|
getLocale: () => {
|
|
35
35
|
const mobileCfg = saltcorn.data.state.getState().mobileConfig;
|
|
36
|
-
return mobileCfg?.language ? mobileCfg.language : "en";
|
|
36
|
+
return mobileCfg?.user?.language ? mobileCfg.user.language : "en";
|
|
37
37
|
},
|
|
38
38
|
user: cfg.user,
|
|
39
39
|
flash: (type, msg) => {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
/*global saltcorn
|
|
1
|
+
/*global saltcorn */
|
|
2
|
+
|
|
3
|
+
import { apiCall } from "../../helpers/api";
|
|
4
|
+
import { setHasOfflineData } from "../../helpers/offline_mode";
|
|
2
5
|
|
|
3
6
|
// post/api/:tableName/:id
|
|
4
|
-
const updateTableRow = async (context) => {
|
|
7
|
+
export const updateTableRow = async (context) => {
|
|
5
8
|
const { tableName, id } = context.params;
|
|
6
9
|
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
7
|
-
const user = {
|
|
8
|
-
id: mobileConfig.user_id,
|
|
9
|
-
role_id: mobileConfig.role_id || 100,
|
|
10
|
-
};
|
|
11
10
|
const table = saltcorn.data.models.Table.findOne({ name: tableName });
|
|
12
11
|
if (!table) throw new Error(`The table '${tableName}' does not exist.`);
|
|
13
12
|
if (
|
|
@@ -24,10 +23,10 @@ const updateTableRow = async (context) => {
|
|
|
24
23
|
id
|
|
25
24
|
);
|
|
26
25
|
if (errors.length > 0) throw new Error(errors.join(", "));
|
|
27
|
-
const ins_res = await table.tryUpdateRow(row, id, user);
|
|
26
|
+
const ins_res = await table.tryUpdateRow(row, id, mobileConfig.user);
|
|
28
27
|
if (ins_res.error)
|
|
29
28
|
throw new Error(`Update '${table.name}' error: ${ins_res.error}`);
|
|
30
|
-
if (mobileConfig.isOfflineMode) await
|
|
29
|
+
if (mobileConfig.isOfflineMode) await setHasOfflineData(true);
|
|
31
30
|
return ins_res;
|
|
32
31
|
} else {
|
|
33
32
|
const response = await apiCall({
|
|
@@ -40,14 +39,10 @@ const updateTableRow = async (context) => {
|
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
// post/api/:tableName
|
|
43
|
-
const insertTableRow = async (context) => {
|
|
42
|
+
export const insertTableRow = async (context) => {
|
|
44
43
|
const { tableName } = context.params;
|
|
45
44
|
const table = saltcorn.data.models.Table.findOne({ name: tableName });
|
|
46
45
|
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
47
|
-
const user = {
|
|
48
|
-
id: mobileConfig.user_id,
|
|
49
|
-
role_id: mobileConfig.role_id || 100,
|
|
50
|
-
};
|
|
51
46
|
if (!table) throw new Error(`The table '${tableName}' does not exist.`);
|
|
52
47
|
if (
|
|
53
48
|
mobileConfig.isOfflineMode ||
|
|
@@ -62,11 +57,11 @@ const insertTableRow = async (context) => {
|
|
|
62
57
|
table.getFields()
|
|
63
58
|
);
|
|
64
59
|
if (errors.length > 0) throw new Error(errors.join(", "));
|
|
65
|
-
const ins_res = await table.tryInsertRow(row, user);
|
|
60
|
+
const ins_res = await table.tryInsertRow(row, mobileConfig.user);
|
|
66
61
|
if (ins_res.error) {
|
|
67
62
|
throw new Error(`Insert '${table.name}' error: ${ins_res.error}`);
|
|
68
63
|
}
|
|
69
|
-
if (mobileConfig.isOfflineMode) await
|
|
64
|
+
if (mobileConfig.isOfflineMode) await setHasOfflineData(true);
|
|
70
65
|
return ins_res;
|
|
71
66
|
} else {
|
|
72
67
|
const response = await apiCall({
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
/*global
|
|
1
|
+
/*global saltcorn */
|
|
2
|
+
import { MobileRequest } from "../mocks/request";
|
|
3
|
+
import { MobileResponse } from "../mocks/response";
|
|
4
|
+
import { apiCall } from "../../helpers/api";
|
|
5
|
+
import { removeJwt } from "../../helpers/auth";
|
|
6
|
+
import { sbAdmin2Layout, getHeaders } from "../utils";
|
|
7
|
+
import { clearHistory } from "../../helpers/navigation";
|
|
2
8
|
|
|
3
9
|
const prepareAuthForm = () => {
|
|
4
10
|
return new saltcorn.data.models.Form({
|
|
@@ -75,7 +81,7 @@ const renderLoginView = async (entryPoint, versionTag, alerts = []) => {
|
|
|
75
81
|
alerts,
|
|
76
82
|
headers: [
|
|
77
83
|
{ css: `static_assets/${versionTag}/saltcorn.css` },
|
|
78
|
-
{ script: "js/
|
|
84
|
+
{ script: "js/iframe_view_utils.js" },
|
|
79
85
|
],
|
|
80
86
|
csrfToken: false,
|
|
81
87
|
});
|
|
@@ -92,13 +98,13 @@ const renderSignupView = (entryPoint, versionTag) => {
|
|
|
92
98
|
alerts: [],
|
|
93
99
|
headers: [
|
|
94
100
|
{ css: `static_assets/${versionTag}/saltcorn.css` },
|
|
95
|
-
{ script: "js/
|
|
101
|
+
{ script: "js/iframe_view_utils.js" },
|
|
96
102
|
],
|
|
97
103
|
csrfToken: false,
|
|
98
104
|
});
|
|
99
105
|
};
|
|
100
106
|
|
|
101
|
-
const getLoginView = async (context) => {
|
|
107
|
+
export const getLoginView = async (context) => {
|
|
102
108
|
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
103
109
|
return {
|
|
104
110
|
content: await renderLoginView(
|
|
@@ -110,7 +116,7 @@ const getLoginView = async (context) => {
|
|
|
110
116
|
};
|
|
111
117
|
};
|
|
112
118
|
|
|
113
|
-
const getSignupView = async () => {
|
|
119
|
+
export const getSignupView = async () => {
|
|
114
120
|
const config = saltcorn.data.state.getState().mobileConfig;
|
|
115
121
|
return {
|
|
116
122
|
content: renderSignupView(config.entry_point, config.version_tag),
|
|
@@ -118,7 +124,7 @@ const getSignupView = async () => {
|
|
|
118
124
|
};
|
|
119
125
|
};
|
|
120
126
|
|
|
121
|
-
const logoutAction = async () => {
|
|
127
|
+
export const logoutAction = async () => {
|
|
122
128
|
const config = saltcorn.data.state.getState().mobileConfig;
|
|
123
129
|
const response = await apiCall({ method: "GET", path: "/auth/logout" });
|
|
124
130
|
if (response.data.success) {
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
/*global
|
|
1
|
+
/*global saltcorn */
|
|
2
|
+
import { apiCall } from "../../helpers/api";
|
|
3
|
+
import { setHasOfflineData, hasOfflineRows } from "../../helpers/offline_mode";
|
|
4
|
+
import i18next from "i18next";
|
|
2
5
|
|
|
3
6
|
// post/delete/:name/:id
|
|
4
|
-
const deleteRows = async (context) => {
|
|
7
|
+
export const deleteRows = async (context) => {
|
|
5
8
|
const { name, id } = context.params;
|
|
6
9
|
const table = await saltcorn.data.models.Table.findOne({ name });
|
|
7
|
-
const { isOfflineMode, localTableIds,
|
|
10
|
+
const { isOfflineMode, localTableIds, user } =
|
|
8
11
|
saltcorn.data.state.getState().mobileConfig;
|
|
9
12
|
if (isOfflineMode || localTableIds.indexOf(table.id) >= 0) {
|
|
10
|
-
if (role_id <= table.min_role_write) {
|
|
13
|
+
if (user.role_id <= table.min_role_write) {
|
|
11
14
|
await table.deleteRows({ id });
|
|
12
15
|
// TODO 'table.is_owner' check?
|
|
13
16
|
} else
|
|
14
17
|
throw new saltcorn.data.utils.NotAuthorized(i18next.t("Not authorized"));
|
|
15
|
-
if (isOfflineMode && (await
|
|
16
|
-
await
|
|
18
|
+
if (isOfflineMode && (await hasOfflineRows()))
|
|
19
|
+
await setHasOfflineData(true);
|
|
17
20
|
// if (isOfflineMode && !(await offlineHelper.hasOfflineRows())) {
|
|
18
21
|
// await offlineHelper.setOfflineSession(null);
|
|
19
22
|
// }
|