@saltcorn/mobile-app 1.5.0-beta.4 → 1.5.0-beta.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.
- package/package.json +1 -1
- package/src/helpers/auth.js +70 -57
- package/src/helpers/notifications.js +6 -0
- package/src/index.js +2 -1
- package/src/init.js +23 -12
- package/src/routing/routes/auth.js +23 -0
- package/www/js/iframe_view_utils.js +18 -0
package/package.json
CHANGED
package/src/helpers/auth.js
CHANGED
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
unregisterPushNotifications,
|
|
13
13
|
} from "../helpers/notifications";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* internal helper for the normal login/signup and public login
|
|
17
|
+
* @param {any} param0
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
15
20
|
async function loginRequest({ email, password, isSignup, isPublic }) {
|
|
16
21
|
const opts = isPublic
|
|
17
22
|
? {
|
|
@@ -39,6 +44,70 @@ async function loginRequest({ email, password, isSignup, isPublic }) {
|
|
|
39
44
|
return response.data;
|
|
40
45
|
}
|
|
41
46
|
|
|
47
|
+
/**
|
|
48
|
+
* helper for normal logins and auth provider logins
|
|
49
|
+
* @param {string} token
|
|
50
|
+
*/
|
|
51
|
+
export async function handleToken(token) {
|
|
52
|
+
const decodedJwt = jwtDecode(token);
|
|
53
|
+
const state = saltcorn.data.state.getState();
|
|
54
|
+
const config = state.mobileConfig;
|
|
55
|
+
config.user = decodedJwt.user;
|
|
56
|
+
config.isPublicUser = false;
|
|
57
|
+
config.isOfflineMode = false;
|
|
58
|
+
await insertUser(config.user);
|
|
59
|
+
await setJwt(token);
|
|
60
|
+
config.jwt = token;
|
|
61
|
+
i18next.changeLanguage(config.user.language);
|
|
62
|
+
const alerts = [];
|
|
63
|
+
if (config.allowOfflineMode) {
|
|
64
|
+
const { offlineUser, hasOfflineData } =
|
|
65
|
+
(await getLastOfflineSession()) || {};
|
|
66
|
+
if (!offlineUser || offlineUser === config.user.email) {
|
|
67
|
+
await sync();
|
|
68
|
+
} else {
|
|
69
|
+
if (hasOfflineData)
|
|
70
|
+
alerts.push({
|
|
71
|
+
type: "warning",
|
|
72
|
+
msg: `'${offlineUser}' has not yet uploaded offline data.`,
|
|
73
|
+
});
|
|
74
|
+
else {
|
|
75
|
+
await deleteOfflineData(true);
|
|
76
|
+
await sync();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (saltcorn.data.utils.isPushEnabled(config.user)) {
|
|
81
|
+
initPushNotifications();
|
|
82
|
+
} else {
|
|
83
|
+
await unregisterPushNotifications();
|
|
84
|
+
}
|
|
85
|
+
alerts.push({
|
|
86
|
+
type: "success",
|
|
87
|
+
msg: i18next.t("Welcome, %s!", {
|
|
88
|
+
postProcess: "sprintf",
|
|
89
|
+
sprintf: [config.user.email],
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
let entryPoint = null;
|
|
94
|
+
if (config.entryPointType === "byrole") {
|
|
95
|
+
const homepageByRole = state.getConfig("home_page_by_role", {})[
|
|
96
|
+
config.user.role_id
|
|
97
|
+
];
|
|
98
|
+
if (homepageByRole) entryPoint = `get/page/${homepageByRole}`;
|
|
99
|
+
else throw new Error("No homepage defined for this role.");
|
|
100
|
+
} else entryPoint = config.entry_point;
|
|
101
|
+
|
|
102
|
+
addRoute({ route: entryPoint, query: undefined });
|
|
103
|
+
const page = await router.resolve({
|
|
104
|
+
pathname: entryPoint,
|
|
105
|
+
fullWrap: true,
|
|
106
|
+
alerts,
|
|
107
|
+
});
|
|
108
|
+
if (page.content) await replaceIframe(page.content, page.isFile);
|
|
109
|
+
}
|
|
110
|
+
|
|
42
111
|
export async function login({ email, password, isSignup }) {
|
|
43
112
|
const loginResult = await loginRequest({
|
|
44
113
|
email,
|
|
@@ -47,63 +116,7 @@ export async function login({ email, password, isSignup }) {
|
|
|
47
116
|
});
|
|
48
117
|
if (typeof loginResult === "string") {
|
|
49
118
|
// use it as a token
|
|
50
|
-
|
|
51
|
-
const state = saltcorn.data.state.getState();
|
|
52
|
-
const config = state.mobileConfig;
|
|
53
|
-
config.user = decodedJwt.user;
|
|
54
|
-
config.isPublicUser = false;
|
|
55
|
-
config.isOfflineMode = false;
|
|
56
|
-
await insertUser(config.user);
|
|
57
|
-
await setJwt(loginResult);
|
|
58
|
-
config.jwt = loginResult;
|
|
59
|
-
i18next.changeLanguage(config.user.language);
|
|
60
|
-
const alerts = [];
|
|
61
|
-
if (config.allowOfflineMode) {
|
|
62
|
-
const { offlineUser, hasOfflineData } =
|
|
63
|
-
(await getLastOfflineSession()) || {};
|
|
64
|
-
if (!offlineUser || offlineUser === config.user.email) {
|
|
65
|
-
await sync();
|
|
66
|
-
} else {
|
|
67
|
-
if (hasOfflineData)
|
|
68
|
-
alerts.push({
|
|
69
|
-
type: "warning",
|
|
70
|
-
msg: `'${offlineUser}' has not yet uploaded offline data.`,
|
|
71
|
-
});
|
|
72
|
-
else {
|
|
73
|
-
await deleteOfflineData(true);
|
|
74
|
-
await sync();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (saltcorn.data.utils.isPushEnabled(config.user)) {
|
|
79
|
-
initPushNotifications();
|
|
80
|
-
} else {
|
|
81
|
-
await unregisterPushNotifications();
|
|
82
|
-
}
|
|
83
|
-
alerts.push({
|
|
84
|
-
type: "success",
|
|
85
|
-
msg: i18next.t("Welcome, %s!", {
|
|
86
|
-
postProcess: "sprintf",
|
|
87
|
-
sprintf: [config.user.email],
|
|
88
|
-
}),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
let entryPoint = null;
|
|
92
|
-
if (config.entryPointType === "byrole") {
|
|
93
|
-
const homepageByRole = state.getConfig("home_page_by_role", {})[
|
|
94
|
-
config.user.role_id
|
|
95
|
-
];
|
|
96
|
-
if (homepageByRole) entryPoint = `get/page/${homepageByRole}`;
|
|
97
|
-
else throw new Error("No homepage defined for this role.");
|
|
98
|
-
} else entryPoint = config.entry_point;
|
|
99
|
-
|
|
100
|
-
addRoute({ route: entryPoint, query: undefined });
|
|
101
|
-
const page = await router.resolve({
|
|
102
|
-
pathname: entryPoint,
|
|
103
|
-
fullWrap: true,
|
|
104
|
-
alerts,
|
|
105
|
-
});
|
|
106
|
-
if (page.content) await replaceIframe(page.content, page.isFile);
|
|
119
|
+
await handleToken(loginResult);
|
|
107
120
|
} else if (loginResult?.alerts) {
|
|
108
121
|
showAlerts(loginResult?.alerts);
|
|
109
122
|
} else {
|
|
@@ -2,6 +2,9 @@ import { Capacitor } from "@capacitor/core";
|
|
|
2
2
|
import { apiCall } from "./api";
|
|
3
3
|
import { showAlerts } from "./common";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @capacitor/push-notifications isn't always included in the build
|
|
7
|
+
*/
|
|
5
8
|
async function loadNotificationsPlugin() {
|
|
6
9
|
try {
|
|
7
10
|
const { PushNotifications } = await import("@capacitor/push-notifications");
|
|
@@ -12,6 +15,9 @@ async function loadNotificationsPlugin() {
|
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @capacitor/device isn't always included in the build
|
|
20
|
+
*/
|
|
15
21
|
async function loadDevicePlugin() {
|
|
16
22
|
try {
|
|
17
23
|
const { Device } = await import("@capacitor/device");
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,8 @@ import { router } from "./routing/index";
|
|
|
12
12
|
const plugins = {};
|
|
13
13
|
const context = require.context("./plugins-code", true, /index\.js$/);
|
|
14
14
|
context.keys().forEach((key) => {
|
|
15
|
-
const
|
|
15
|
+
const tokens = key.split("/");
|
|
16
|
+
const pluginName = tokens[tokens.length - 2];
|
|
16
17
|
plugins[pluginName] = context(key);
|
|
17
18
|
});
|
|
18
19
|
|
package/src/init.js
CHANGED
|
@@ -219,17 +219,6 @@ const onResume = async () => {
|
|
|
219
219
|
await startOfflineMode();
|
|
220
220
|
clearHistory();
|
|
221
221
|
if (mobileConfig.user?.id) await gotoEntryView();
|
|
222
|
-
else {
|
|
223
|
-
const decodedJwt = jwtDecode(mobileConfig.jwt);
|
|
224
|
-
mobileConfig.user = decodedJwt.user;
|
|
225
|
-
mobileConfig.isPublicUser = false;
|
|
226
|
-
}
|
|
227
|
-
addRoute({ route: mobileConfig.entry_point, query: undefined });
|
|
228
|
-
const page = await router.resolve({
|
|
229
|
-
pathname: mobileConfig.entry_point,
|
|
230
|
-
fullWrap: true,
|
|
231
|
-
alerts: [],
|
|
232
|
-
});
|
|
233
222
|
} catch (error) {
|
|
234
223
|
await showErrorPage(error);
|
|
235
224
|
}
|
|
@@ -337,7 +326,7 @@ const readSchemaIfNeeded = async () => {
|
|
|
337
326
|
}
|
|
338
327
|
};
|
|
339
328
|
|
|
340
|
-
const readSiteLogo = async (
|
|
329
|
+
const readSiteLogo = async () => {
|
|
341
330
|
if (Capacitor.platform === "web") return "";
|
|
342
331
|
try {
|
|
343
332
|
const base64 = await readTextCordova(
|
|
@@ -381,6 +370,28 @@ export async function init(mobileConfig) {
|
|
|
381
370
|
await saltcorn.mobileApp.navigation.goBack(1, true);
|
|
382
371
|
});
|
|
383
372
|
|
|
373
|
+
App.addListener("appUrlOpen", async (event) => {
|
|
374
|
+
try {
|
|
375
|
+
const url = event.url;
|
|
376
|
+
if (url.startsWith("mobileapp://auth/callback")) {
|
|
377
|
+
const token = new URL(url).searchParams.get("token");
|
|
378
|
+
const method = new URL(url).searchParams.get("method");
|
|
379
|
+
const methods = saltcorn.data.state.getState().auth_methods;
|
|
380
|
+
if (!methods[method])
|
|
381
|
+
throw new Error(`Authentication method '${method}' not found.`);
|
|
382
|
+
const modName = methods[method].module_name;
|
|
383
|
+
if (!modName)
|
|
384
|
+
throw new Error(`Module name for '${method}' is not defined.`);
|
|
385
|
+
const authModule = saltcorn.mobileApp.plugins[modName];
|
|
386
|
+
if (!authModule)
|
|
387
|
+
throw new Error(`Authentication module '${modName}' not found.`);
|
|
388
|
+
await authModule.finishLogin(token);
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
await showErrorPage(error);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
384
395
|
const lastLocation = takeLastLocation();
|
|
385
396
|
document.addEventListener("resume", onResume, false);
|
|
386
397
|
await addScripts(mobileConfig.version_tag);
|
|
@@ -38,6 +38,15 @@ const getAuthLinks = (current, entryPoint) => {
|
|
|
38
38
|
links.signup = "javascript:execLink('/auth/signup')";
|
|
39
39
|
if (state.getConfig("public_user_link"))
|
|
40
40
|
links.publicUser = `javascript:publicLogin('${entryPoint}')`;
|
|
41
|
+
for (const [name, auth] of Object.entries(state.auth_methods)) {
|
|
42
|
+
links.methods.push({
|
|
43
|
+
icon: auth.icon,
|
|
44
|
+
label: auth.label,
|
|
45
|
+
name,
|
|
46
|
+
url: `javascript:loginWith('${name}')`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
return links;
|
|
42
51
|
};
|
|
43
52
|
|
|
@@ -87,6 +96,7 @@ const renderLoginView = async (entryPoint, versionTag, alerts = []) => {
|
|
|
87
96
|
...(roleHeaders ? roleHeaders : []),
|
|
88
97
|
],
|
|
89
98
|
csrfToken: false,
|
|
99
|
+
req: new MobileRequest(),
|
|
90
100
|
});
|
|
91
101
|
};
|
|
92
102
|
|
|
@@ -107,6 +117,11 @@ const renderSignupView = (entryPoint, versionTag) => {
|
|
|
107
117
|
});
|
|
108
118
|
};
|
|
109
119
|
|
|
120
|
+
/**
|
|
121
|
+
*
|
|
122
|
+
* @param {*} context
|
|
123
|
+
* @returns
|
|
124
|
+
*/
|
|
110
125
|
export const getLoginView = async (context) => {
|
|
111
126
|
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
112
127
|
return {
|
|
@@ -119,6 +134,10 @@ export const getLoginView = async (context) => {
|
|
|
119
134
|
};
|
|
120
135
|
};
|
|
121
136
|
|
|
137
|
+
/**
|
|
138
|
+
*
|
|
139
|
+
* @returns
|
|
140
|
+
*/
|
|
122
141
|
export const getSignupView = async () => {
|
|
123
142
|
const config = saltcorn.data.state.getState().mobileConfig;
|
|
124
143
|
return {
|
|
@@ -127,6 +146,10 @@ export const getSignupView = async () => {
|
|
|
127
146
|
};
|
|
128
147
|
};
|
|
129
148
|
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
130
153
|
export const logoutAction = async () => {
|
|
131
154
|
const config = saltcorn.data.state.getState().mobileConfig;
|
|
132
155
|
const response = await apiCall({ method: "GET", path: "/auth/logout" });
|
|
@@ -287,6 +287,24 @@ async function loginFormSubmit(e, entryView) {
|
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
async function loginWith(strategyName) {
|
|
291
|
+
try {
|
|
292
|
+
console.log("login with", strategyName);
|
|
293
|
+
const methods = parent.saltcorn.data.state.getState().auth_methods;
|
|
294
|
+
if (!methods[strategyName])
|
|
295
|
+
throw new Error(`No such auth strategy: ${strategyName}`);
|
|
296
|
+
const modName = methods[strategyName].module_name;
|
|
297
|
+
if (!modName)
|
|
298
|
+
throw new Error(`Module name for '${strategyName}' is not defined.`);
|
|
299
|
+
const authModule = parent.saltcorn.mobileApp.plugins[modName];
|
|
300
|
+
if (!authModule)
|
|
301
|
+
throw new Error(`Authentication module '${modName}' not found.`);
|
|
302
|
+
await authModule.startLogin(strategyName);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
parent.saltcorn.mobileApp.common.errorAlert(error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
290
308
|
async function local_post_btn(e) {
|
|
291
309
|
try {
|
|
292
310
|
showLoadSpinner();
|