@saltcorn/mobile-app 1.6.0-beta.3 → 1.6.0-beta.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@saltcorn/mobile-app",
3
3
  "displayName": "Saltcorn mobile app",
4
- "version": "1.6.0-beta.3",
4
+ "version": "1.6.0-beta.4",
5
5
  "description": "Saltcorn mobile app for Android and iOS",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -1,6 +1,9 @@
1
1
  /*global saltcorn, splashConfig*/
2
2
 
3
3
  import axios from "axios";
4
+ import { jwtDecode } from "jwt-decode";
5
+ import { router } from "../routing/index";
6
+ import { replaceIframe, clearHistory } from "./navigation";
4
7
 
5
8
  export async function apiCall({
6
9
  method,
@@ -24,7 +27,28 @@ export async function apiCall({
24
27
  };
25
28
  if (config.tenantAppName) headers["X-Saltcorn-App"] = config.tenantAppName;
26
29
  const token = config.jwt;
27
- if (token) headers.Authorization = `jwt ${token}`;
30
+ if (token) {
31
+ try {
32
+ const decoded = jwtDecode(token);
33
+ if (decoded.exp && decoded.exp < Date.now() / 1000 && typeof saltcorn !== "undefined" && !path.startsWith("/auth/")) {
34
+ const mobileConfig = saltcorn.data.state.getState().mobileConfig;
35
+ mobileConfig.jwt = undefined;
36
+ await saltcorn.data.db.deleteWhere("jwt_table");
37
+ clearHistory();
38
+ const page = await router.resolve({
39
+ pathname: "get/auth/login",
40
+ alerts: [
41
+ { type: "warning", msg: "Your session has expired, please log in again." },
42
+ ],
43
+ });
44
+ await replaceIframe(page.content);
45
+ return;
46
+ }
47
+ } catch {
48
+ // malformed token, let the request proceed and fail naturally
49
+ }
50
+ headers.Authorization = `jwt ${token}`;
51
+ }
28
52
  try {
29
53
  const result = await axios({
30
54
  url: url,
@@ -26,22 +26,22 @@ async function loginRequest({ email, password, isSignup, isPublic }) {
26
26
  path: "/auth/login-with/jwt",
27
27
  }
28
28
  : isSignup
29
- ? {
30
- method: "POST",
31
- path: "/auth/signup",
32
- body: {
33
- email,
34
- password,
35
- },
36
- }
37
- : {
38
- method: "GET",
39
- path: "/auth/login-with/jwt",
40
- params: {
41
- email,
42
- password,
43
- },
44
- };
29
+ ? {
30
+ method: "POST",
31
+ path: "/auth/signup",
32
+ body: {
33
+ email,
34
+ password,
35
+ },
36
+ }
37
+ : {
38
+ method: "POST",
39
+ path: "/auth/login-with/jwt",
40
+ body: {
41
+ email,
42
+ password,
43
+ },
44
+ };
45
45
  const response = await apiCall(opts);
46
46
  return response.data;
47
47
  }
@@ -241,13 +241,34 @@ export async function setJwt(jwt) {
241
241
  await saltcorn.data.db.insert("jwt_table", { jwt: jwt });
242
242
  }
243
243
 
244
- export async function checkJWT(jwt) {
245
- if (jwt && jwt !== "undefined") {
244
+ async function renewJwt() {
245
+ try {
246
+ const response = await apiCall({
247
+ method: "POST",
248
+ path: "/auth/renew-jwt",
249
+ });
250
+ if (typeof response.data === "string") {
251
+ const config = saltcorn.data.state.getState().mobileConfig;
252
+ await setJwt(response.data);
253
+ config.jwt = response.data;
254
+ }
255
+ } catch (e) {
256
+ console.error("JWT renewal failed:", e.message);
257
+ }
258
+ }
259
+
260
+ export async function checkJWT(jwtStr) {
261
+ if (jwtStr && jwtStr !== "undefined") {
246
262
  const response = await apiCall({
247
263
  method: "GET",
248
264
  path: "/auth/authenticated",
249
265
  timeout: 10000,
250
266
  });
251
- return response.data.authenticated;
267
+ if (!response.data.authenticated) return false;
268
+ const decoded = jwtDecode(jwtStr);
269
+ if (decoded.exp && decoded.exp - Date.now() / 1000 < 7 * 24 * 3600) {
270
+ await renewJwt();
271
+ }
272
+ return true;
252
273
  } else return false;
253
274
  }
package/src/init.js CHANGED
@@ -211,10 +211,14 @@ const showErrorPage = async (error) => {
211
211
  const onResume = async () => {
212
212
  if (typeof saltcorn === "undefined") return;
213
213
  const mobileConfig = saltcorn.data.state.getState().mobileConfig;
214
- if (mobileConfig?.allowOfflineMode && mobileConfig.jwt) {
214
+ if (mobileConfig?.jwt) {
215
215
  const netStatus = await Network.getStatus();
216
216
  mobileConfig.networkState = netStatus.connectionType;
217
- if (mobileConfig.networkState === "none" && !mobileConfig.isOfflineMode) {
217
+ if (
218
+ mobileConfig.allowOfflineMode &&
219
+ mobileConfig.networkState === "none" &&
220
+ !mobileConfig.isOfflineMode
221
+ ) {
218
222
  try {
219
223
  await startOfflineMode();
220
224
  clearHistory();
@@ -222,14 +226,16 @@ const onResume = async () => {
222
226
  } catch (error) {
223
227
  await showErrorPage(error);
224
228
  }
225
- } else if (
226
- mobileConfig.networkState !== "none" &&
227
- mobileConfig.syncOnAppResume
228
- ) {
229
- try {
230
- await sync(false, false, []);
231
- } catch (error) {
232
- await showErrorPage(error);
229
+ } else if (mobileConfig.networkState !== "none") {
230
+ if (!mobileConfig.isOfflineMode && !isPublicJwt(mobileConfig.jwt)) {
231
+ await checkJWT(mobileConfig.jwt);
232
+ }
233
+ if (mobileConfig.allowOfflineMode && mobileConfig.syncOnAppResume) {
234
+ try {
235
+ await sync(false, false, []);
236
+ } catch (error) {
237
+ await showErrorPage(error);
238
+ }
233
239
  }
234
240
  }
235
241
  }