@saltcorn/mobile-app 1.5.0-beta.9 → 1.5.0-rc.1

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/src/init.js CHANGED
@@ -22,7 +22,11 @@ import {
22
22
  gotoEntryView,
23
23
  addRoute,
24
24
  } from "./helpers/navigation.js";
25
- import { checkSendIntentReceived } from "./helpers/common.js";
25
+ import {
26
+ checkSendIntentReceived,
27
+ tryInitBackgroundSync,
28
+ tryInitPush,
29
+ } from "./helpers/common.js";
26
30
  import { readJSONCordova, readTextCordova } from "./helpers/file_system.js";
27
31
 
28
32
  import i18next from "i18next";
@@ -443,6 +447,7 @@ export async function init(mobileConfig) {
443
447
  const jwt = state.mobileConfig.jwt;
444
448
  const alerts = [];
445
449
  if ((networkDisabled && jwt) || (await checkJWT(jwt))) {
450
+ // already logged in, continue
446
451
  const mobileConfig = state.mobileConfig;
447
452
  const decodedJwt = jwtDecode(mobileConfig.jwt);
448
453
  mobileConfig.user = decodedJwt.user;
@@ -467,7 +472,7 @@ export async function init(mobileConfig) {
467
472
  }
468
473
  } else if (offlineUser) {
469
474
  if (offlineUser === mobileConfig.user.email) {
470
- await sync();
475
+ await sync(true, true, alerts);
471
476
  alerts.push({
472
477
  type: "info",
473
478
  msg: "Synchronized your offline data.",
@@ -478,7 +483,7 @@ export async function init(mobileConfig) {
478
483
  msg: `'${offlineUser}' has not yet uploaded offline data.`,
479
484
  });
480
485
  } else {
481
- await sync();
486
+ await sync(true, true, alerts);
482
487
  alerts.push({
483
488
  type: "info",
484
489
  msg: "Synchronized your offline data.",
@@ -486,6 +491,9 @@ export async function init(mobileConfig) {
486
491
  }
487
492
  }
488
493
 
494
+ await tryInitPush(mobileConfig);
495
+ await tryInitBackgroundSync(mobileConfig);
496
+
489
497
  if (Capacitor.getPlatform() === "ios") {
490
498
  const shareData = await checkSendIntentReceived();
491
499
  if (shareData && notEmpty(shareData)) return await postShare(shareData);
@@ -518,6 +526,7 @@ export async function init(mobileConfig) {
518
526
  }
519
527
  if (page.content) await replaceIframe(page.content, page.isFile);
520
528
  } else if (isPublicJwt(jwt)) {
529
+ // already logged in as public
521
530
  const config = state.mobileConfig;
522
531
  config.user = { role_id: 100, email: "public", language: "en" };
523
532
  config.isPublicUser = true;
@@ -539,13 +548,17 @@ export async function init(mobileConfig) {
539
548
  )) &&
540
549
  state.mobileConfig.autoPublicLogin
541
550
  ) {
551
+ // try autoPublicLogin
542
552
  if (networkDisabled)
543
553
  throw new Error(
544
554
  "No internet connection or previous login is available. " +
545
555
  "Please go online and reload, the public login is not yet supported."
546
556
  );
547
557
  await publicLogin(getEntryPoint(100, state, state.mobileConfig));
548
- } else await showLogin(alerts);
558
+ } else {
559
+ // open login page
560
+ await showLogin(alerts);
561
+ }
549
562
  } catch (error) {
550
563
  if (typeof saltcorn === "undefined" || typeof router === "undefined") {
551
564
  const msg = `An error occured: ${
@@ -6,7 +6,7 @@ import {
6
6
  updateTableRow,
7
7
  insertTableRow,
8
8
  } from "./routes/api";
9
- import { getLoginView, logoutAction, getSignupView } from "./routes/auth";
9
+ import { getLoginView, getSignupView } from "./routes/auth";
10
10
  import { deleteRows } from "./routes/delete";
11
11
  import { postToggleField } from "./routes/edit";
12
12
  import { getErrorView } from "./routes/error";
@@ -54,16 +54,12 @@ const routes = [
54
54
  path: "get/auth/login",
55
55
  action: getLoginView,
56
56
  },
57
- {
58
- path: "get/auth/logout",
59
- action: logoutAction,
60
- },
61
57
  {
62
58
  path: "get/auth/signup",
63
59
  action: getSignupView,
64
60
  },
61
+
65
62
  // delete
66
-
67
63
  {
68
64
  path: "post/delete/:tableName/:id", // legacy
69
65
  action: deleteRows,
@@ -72,7 +68,7 @@ const routes = [
72
68
  path: "delete/api/:tableName/:id",
73
69
  action: deleteRows,
74
70
  },
75
-
71
+
76
72
  // edit
77
73
  {
78
74
  path: "post/edit/toggle/:name/:id/:field_name",
@@ -1,11 +1,12 @@
1
1
  /*global saltcorn */
2
2
  import { MobileRequest } from "../mocks/request";
3
3
  import { MobileResponse } from "../mocks/response";
4
- import { apiCall } from "../../helpers/api";
5
- import { removeJwt } from "../../helpers/auth";
6
4
  import { sbAdmin2Layout, getHeaders } from "../utils";
7
- import { clearHistory } from "../../helpers/navigation";
8
5
 
6
+ /**
7
+ * internal helper to prepare the login or signup form
8
+ * @returns
9
+ */
9
10
  const prepareAuthForm = () => {
10
11
  return new saltcorn.data.models.Form({
11
12
  class: "login",
@@ -29,7 +30,13 @@ const prepareAuthForm = () => {
29
30
  });
30
31
  };
31
32
 
32
- // TODO delete this and integrate getAuthLinks() from '/server/auth/routes.js'
33
+ /**
34
+ * internal helper to get auth links
35
+ * TODO delete this and integrate getAuthLinks() from '/server/auth/routes.js'
36
+ * @param {*} current
37
+ * @param {*} entryPoint
38
+ * @returns
39
+ */
33
40
  const getAuthLinks = (current, entryPoint) => {
34
41
  const links = { methods: [] };
35
42
  const state = saltcorn.data.state.getState();
@@ -51,6 +58,13 @@ const getAuthLinks = (current, entryPoint) => {
51
58
  return links;
52
59
  };
53
60
 
61
+ /**
62
+ * internal helper to render login view
63
+ * @param {*} entryPoint
64
+ * @param {*} versionTag
65
+ * @param {*} alerts
66
+ * @returns
67
+ */
54
68
  const renderLoginView = async (entryPoint, versionTag, alerts = []) => {
55
69
  const state = saltcorn.data.state.getState();
56
70
  const form = prepareAuthForm(entryPoint);
@@ -101,6 +115,12 @@ const renderLoginView = async (entryPoint, versionTag, alerts = []) => {
101
115
  });
102
116
  };
103
117
 
118
+ /**
119
+ * internal helper to render signup view
120
+ * @param {*} entryPoint
121
+ * @param {*} versionTag
122
+ * @returns
123
+ */
104
124
  const renderSignupView = (entryPoint, versionTag) => {
105
125
  const form = prepareAuthForm(entryPoint);
106
126
  form.onSubmit = `javascript:signupFormSubmit(this, '${entryPoint}')`;
@@ -146,23 +166,3 @@ export const getSignupView = async () => {
146
166
  replaceIframe: true,
147
167
  };
148
168
  };
149
-
150
- /**
151
- *
152
- * @returns
153
- */
154
- export const logoutAction = async () => {
155
- const config = saltcorn.data.state.getState().mobileConfig;
156
- const response = await apiCall({ method: "GET", path: "/auth/logout" });
157
- if (response.data.success) {
158
- await removeJwt();
159
- clearHistory();
160
- config.jwt = undefined;
161
- return {
162
- content: await renderLoginView(config.entry_point, config.version_tag),
163
- };
164
- } else {
165
- console.log("unable to logout");
166
- return {};
167
- }
168
- };
@@ -192,7 +192,7 @@ async function inline_local_submit(e, opts1) {
192
192
  });
193
193
  inline_submit_success(e, form, opts);
194
194
  } catch (error) {
195
- parent.saltcorn.mobileApp.common.showAlerts([
195
+ parent.saltcorn.mobileApp.common.showToasts([
196
196
  {
197
197
  type: "error",
198
198
  msg: error.message ? error.message : "An error occured.",
@@ -565,7 +565,7 @@ async function mobile_modal(url, opts = {}) {
565
565
  // onOpen onClose initialize_page?
566
566
  }
567
567
  } catch (error) {
568
- parent.saltcorn.mobileApp.common.showAlerts([
568
+ parent.saltcorn.mobileApp.common.showToasts([
569
569
  {
570
570
  type: "error",
571
571
  msg: error.message ? error.message : "An error occured.",
@@ -665,7 +665,7 @@ async function make_unique_field(
665
665
  );
666
666
  }
667
667
  } catch (error) {
668
- parent.saltcorn.mobileApp.common.showAlerts([
668
+ parent.saltcorn.mobileApp.common.showToasts([
669
669
  {
670
670
  type: "error",
671
671
  msg: "unable to 'make_unique_field'",
@@ -835,16 +835,13 @@ async function switchNetworkMode() {
835
835
  parent.saltcorn.mobileApp.navigation.addRoute({
836
836
  route: "get/sync/sync_settings",
837
837
  });
838
- parent.saltcorn.mobileApp.common.showAlerts(
839
- [
840
- {
841
- type: "info",
842
- msg: parent.saltcorn.mobileApp.offlineMode.getOfflineMsg(),
843
- },
844
- ],
845
- false
846
- );
847
- parent.saltcorn.mobileApp.common.clearAlerts();
838
+ parent.saltcorn.mobileApp.common.showAlerts([
839
+ {
840
+ type: "info",
841
+ msg: parent.saltcorn.mobileApp.offlineMode.getOfflineMsg(),
842
+ },
843
+ ]);
844
+ parent.saltcorn.mobileApp.common.clearToasts();
848
845
  } else {
849
846
  if (networkState === "none")
850
847
  throw new Error("No internet connection is available.");
@@ -854,16 +851,16 @@ async function switchNetworkMode() {
854
851
  parent.saltcorn.mobileApp.navigation.addRoute({
855
852
  route: "get/sync/sync_settings",
856
853
  });
857
- parent.saltcorn.mobileApp.common.showAlerts([
854
+ parent.saltcorn.mobileApp.common.showToasts([
858
855
  {
859
856
  type: "info",
860
857
  msg: "You are online again.",
861
858
  },
862
859
  ]);
863
- parent.saltcorn.mobileApp.common.clearTopAlerts();
860
+ parent.saltcorn.mobileApp.common.clearAlerts();
864
861
  }
865
862
  } catch (error) {
866
- parent.saltcorn.mobileApp.common.showAlerts([
863
+ parent.saltcorn.mobileApp.common.showToasts([
867
864
  {
868
865
  type: "error",
869
866
  msg: `Unable to change the network mode: ${
@@ -883,7 +880,7 @@ async function callSync() {
883
880
  try {
884
881
  const mobileConfig = parent.saltcorn.data.state.getState().mobileConfig;
885
882
  if (mobileConfig.networkState === "none") {
886
- parent.saltcorn.mobileApp.common.showAlerts([
883
+ parent.saltcorn.mobileApp.common.showToasts([
887
884
  {
888
885
  type: "error",
889
886
  msg: "You don't have an internet connection.",
@@ -892,15 +889,15 @@ async function callSync() {
892
889
  } else {
893
890
  const wasOffline = mobileConfig.isOfflineMode;
894
891
  showLoadSpinner();
895
- await parent.saltcorn.mobileApp.offlineMode.sync();
896
- parent.saltcorn.mobileApp.common.clearAlerts();
892
+ const alerts = [];
893
+ await parent.saltcorn.mobileApp.offlineMode.sync(true, true, alerts);
894
+ parent.saltcorn.mobileApp.common.clearToasts();
897
895
  if (!wasOffline) {
898
- parent.saltcorn.mobileApp.common.showAlerts([
899
- {
900
- type: "info",
901
- msg: "Synchronized your offline data.",
902
- },
903
- ]);
896
+ alerts.push({
897
+ type: "info",
898
+ msg: "Synchronized your offline data.",
899
+ });
900
+ parent.saltcorn.mobileApp.common.showToasts(alerts);
904
901
  } else {
905
902
  setNetworSwitcherOn();
906
903
  parent.saltcorn.mobileApp.navigation.clearHistory();
@@ -908,13 +905,12 @@ async function callSync() {
908
905
  parent.saltcorn.mobileApp.navigation.addRoute({
909
906
  route: "get/sync/sync_settings",
910
907
  });
911
- parent.saltcorn.mobileApp.common.showAlerts([
912
- {
913
- type: "info",
914
- msg: "Synchronized your offline data, you are online again.",
915
- },
916
- ]);
917
- parent.saltcorn.mobileApp.common.clearTopAlerts();
908
+ alerts.push({
909
+ type: "info",
910
+ msg: "Synchronized your offline data, you are online again.",
911
+ });
912
+ parent.saltcorn.mobileApp.common.showToasts(alerts);
913
+ parent.saltcorn.mobileApp.common.clearAlerts();
918
914
  }
919
915
  }
920
916
  } catch (error) {
@@ -930,14 +926,14 @@ async function deleteOfflineDataClicked() {
930
926
  await parent.saltcorn.mobileApp.offlineMode.getLastOfflineSession();
931
927
  const { user } = parent.saltcorn.data.state.getState().mobileConfig;
932
928
  if (!lastOfflineSession?.offlineUser) {
933
- parent.saltcorn.mobileApp.common.showAlerts([
929
+ parent.saltcorn.mobileApp.common.showToasts([
934
930
  {
935
931
  type: "error",
936
932
  msg: "You don't have any offline data.",
937
933
  },
938
934
  ]);
939
935
  } else if (lastOfflineSession.offlineUser !== user.email) {
940
- parent.saltcorn.mobileApp.common.showAlerts([
936
+ parent.saltcorn.mobileApp.common.showToasts([
941
937
  {
942
938
  type: "error",
943
939
  msg: `The offline data is owned by '${lastOfflineSession.offlineUser}'.`,
@@ -1,53 +0,0 @@
1
- const { parseStringPromise, Builder } = require("xml2js");
2
- const { join } = require("path");
3
- const { readFileSync, writeFileSync } = require("fs");
4
-
5
- const readMobileConfig = () => {
6
- console.log("Reading mobile config");
7
- const content = readFileSync(
8
- "/saltcorn-mobile-app/saltcorn-mobile-cfg.json",
9
- "utf8"
10
- );
11
- console.log(content);
12
- return JSON.parse(content);
13
- };
14
-
15
- (async () => {
16
- try {
17
- const { permissions, features } = readMobileConfig();
18
- const androidManifest = join(
19
- "android",
20
- "app",
21
- "src",
22
- "main",
23
- "AndroidManifest.xml"
24
- );
25
- const content = readFileSync(androidManifest);
26
- const parsed = await parseStringPromise(content);
27
-
28
- parsed.manifest["uses-permission"] = permissions.map((p) => ({
29
- $: { "android:name": p },
30
- }));
31
- parsed.manifest["uses-feature"] = features.map((f) => ({
32
- $: { "android:name": f },
33
- }));
34
-
35
- parsed.manifest.application[0].$ = {
36
- ...parsed.manifest.application[0].$,
37
- "android:allowBackup": "false",
38
- "android:fullBackupContent": "false",
39
- "android:dataExtractionRules": "@xml/data_extraction_rules",
40
- "android:networkSecurityConfig": "@xml/network_security_config",
41
- "android:usesCleartextTraffic": "true",
42
- };
43
- const xmlBuilder = new Builder();
44
- const newCfg = xmlBuilder.buildObject(parsed);
45
- writeFileSync(androidManifest, newCfg);
46
- } catch (error) {
47
- console.log(
48
- `Unable to modify the AndroidManifest.xml: ${
49
- error.message ? error.message : "Unknown error"
50
- }`
51
- );
52
- }
53
- })();
@@ -1,21 +0,0 @@
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");
@@ -1,92 +0,0 @@
1
- import { Capacitor } from "@capacitor/core";
2
- import { apiCall } from "./api";
3
- import { showAlerts } from "./common";
4
-
5
- /**
6
- * @capacitor/push-notifications isn't always included in the build
7
- */
8
- async function loadNotificationsPlugin() {
9
- try {
10
- const { PushNotifications } = await import("@capacitor/push-notifications");
11
- return PushNotifications;
12
- } catch (error) {
13
- console.warn("Error loading PushNotifications plugin:", error);
14
- return null;
15
- }
16
- }
17
-
18
- /**
19
- * @capacitor/device isn't always included in the build
20
- */
21
- async function loadDevicePlugin() {
22
- try {
23
- const { Device } = await import("@capacitor/device");
24
- return Device;
25
- } catch (error) {
26
- console.warn("Error loading Device plugin:", error);
27
- return null;
28
- }
29
- }
30
-
31
- async function uploadFcmToken(token, deviceId) {
32
- try {
33
- const response = await apiCall({
34
- method: "POST",
35
- path: "/notifications/fcm-token",
36
- body: { token, deviceId },
37
- });
38
- const data = response.data;
39
- if (data.success.success === "ok")
40
- console.log("Token uploaded successfully:", data);
41
- else console.error("Unable to upload token:", data);
42
- } catch (error) {
43
- console.error("Error uploading token:", error);
44
- }
45
- }
46
-
47
- export async function initPushNotifications() {
48
- const PushNotifications = await loadNotificationsPlugin();
49
- if (Capacitor.getPlatform() !== "web" && PushNotifications) {
50
- const { Device } = await loadDevicePlugin();
51
- const permStatus = await PushNotifications.requestPermissions();
52
- if (permStatus.receive === "granted") {
53
- await PushNotifications.register();
54
- PushNotifications.addListener("registration", async (token) => {
55
- const { identifier } = await Device.getId();
56
- await uploadFcmToken(token.value, identifier);
57
- });
58
-
59
- PushNotifications.addListener("registrationError", (err) => {
60
- console.error("Push registration error:", err);
61
- });
62
-
63
- PushNotifications.addListener(
64
- "pushNotificationReceived",
65
- (notification) => {
66
- console.log("Push received in foreground:", notification);
67
- showAlerts([
68
- {
69
- type: "info",
70
- msg: notification.body,
71
- title: notification.title,
72
- },
73
- ]);
74
- }
75
- );
76
- } else {
77
- console.warn("Push notification permission not granted");
78
- }
79
- }
80
- }
81
-
82
- export async function unregisterPushNotifications() {
83
- const PushNotifications = await loadNotificationsPlugin();
84
- if (Capacitor.getPlatform() !== "web" && PushNotifications) {
85
- try {
86
- await PushNotifications.unregister();
87
- console.log("Push notifications unregistered successfully");
88
- } catch (error) {
89
- console.error("Error unregistering push notifications:", error);
90
- }
91
- }
92
- }
File without changes