@protobase/ui 0.40.8 → 0.40.9
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/dist/app/app.d.ts +4 -1
- package/dist/app/app.js +2 -1
- package/dist/app/main.js +3 -1
- package/dist/app/native-auth-fetch.d.ts +8 -0
- package/dist/app/native-auth-fetch.js +53 -0
- package/dist/auth/auth-provider.js +16 -5
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-DA4sAj85.js +9 -0
- package/dist/shell/assets/index-DORKU14t.js +2 -0
- package/dist/shell/assets/{records-map-DW4-2p9A.js → records-map-BWPZWDHO.js} +2 -2
- package/dist/shell/assets/{shared-protobase-ui-DOLAGMZi.js → shared-protobase-ui-KwzlPgvP.js} +2 -2
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-DA4sAj85.js.map +1 -0
- package/dist/shell-source-maps/assets/index-DORKU14t.js.map +1 -0
- package/dist/shell-source-maps/assets/{records-map-DW4-2p9A.js.map → records-map-BWPZWDHO.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-DOLAGMZi.js.map → shared-protobase-ui-KwzlPgvP.js.map} +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-DXbtlR1O.js +0 -9
- package/dist/shell/assets/index-hbOjGIrj.js +0 -2
- package/dist/shell-source-maps/assets/app-DXbtlR1O.js.map +0 -1
- package/dist/shell-source-maps/assets/index-hbOjGIrj.js.map +0 -1
package/dist/app/app.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { SidebarMode } from "../app-shell/sidebar-mode.js";
|
|
|
2
2
|
import "../app-shell/index.js";
|
|
3
3
|
import { ProjectUi } from "./pages/project-ui.js";
|
|
4
4
|
import { PluginLoader } from "../plugins/load-plugin.js";
|
|
5
|
+
import { NativeBridge } from "./native-auth-fetch.js";
|
|
5
6
|
import { Locale } from "@protobase/schema/internal";
|
|
6
7
|
import { AssistantClient, AuthSession, Client, RuntimeClient } from "@protobase/client";
|
|
7
8
|
//#region src/app/app.d.ts
|
|
@@ -30,11 +31,13 @@ export type AppProps = {
|
|
|
30
31
|
pluginLoader?: PluginLoader;
|
|
31
32
|
/** The language the screens are in when the app's `/meta` sets none, such as the browser's (`browserLocale()`); English by default. */
|
|
32
33
|
locale?: Locale;
|
|
34
|
+
/** The native bridge of the iOS or Android app (`nativeBridgeOf`): sign-in goes through native HTTP. */
|
|
35
|
+
nativeBridge?: NativeBridge;
|
|
33
36
|
};
|
|
34
37
|
/**
|
|
35
38
|
* The admin: a public form at `/-/forms/<name>` for anyone, the sign-in route first, `/-/sign-in`, or browsing as a
|
|
36
39
|
* visitor when the project's anonymous role reads, then a sidebar from `/meta`, `/:page` composed pages, `/:resource`
|
|
37
40
|
* lists and `/:resource/:key` records. Fills its parent, in the theme this browser chose, light, dark or the system's.
|
|
38
41
|
*/
|
|
39
|
-
export declare const App: ({ auth, workspace, basePath, initialUrl, pluginLoader, locale, ...rest }: AppProps) => import("react").JSX.Element;
|
|
42
|
+
export declare const App: ({ auth, workspace, basePath, initialUrl, pluginLoader, locale, nativeBridge, ...rest }: AppProps) => import("react").JSX.Element;
|
|
40
43
|
//#endregion
|
package/dist/app/app.js
CHANGED
|
@@ -328,11 +328,12 @@ const PublicFormGate = ({ baseUrl, client, ui, children }) => {
|
|
|
328
328
|
* visitor when the project's anonymous role reads, then a sidebar from `/meta`, `/:page` composed pages, `/:resource`
|
|
329
329
|
* lists and `/:resource/:key` records. Fills its parent, in the theme this browser chose, light, dark or the system's.
|
|
330
330
|
*/
|
|
331
|
-
const App = ({ auth, workspace, basePath, initialUrl, pluginLoader, locale, ...rest }) => {
|
|
331
|
+
const App = ({ auth, workspace, basePath, initialUrl, pluginLoader, locale, nativeBridge, ...rest }) => {
|
|
332
332
|
setFallbackLocale(locale ?? "en");
|
|
333
333
|
return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
|
|
334
334
|
session: auth,
|
|
335
335
|
...rest.baseUrl && { origin: new URL(rest.baseUrl, window.location.href).origin },
|
|
336
|
+
...nativeBridge && { nativeBridge },
|
|
336
337
|
children: /* @__PURE__ */ jsx(Router, {
|
|
337
338
|
basePath,
|
|
338
339
|
initialUrl,
|
package/dist/app/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { browserLocale } from "../locale/active-locale.js";
|
|
2
|
+
import { nativeBridgeOf } from "./native-auth-fetch.js";
|
|
2
3
|
import { uiFromModule } from "./pages/ui-from-module.js";
|
|
3
4
|
import { App } from "./app.js";
|
|
4
5
|
import { pageApiOrigin } from "./page-api-origin.js";
|
|
@@ -13,6 +14,7 @@ if (!root) throw new Error("index.html needs an element with id \"root\"");
|
|
|
13
14
|
createRoot(root).render(/* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(App, {
|
|
14
15
|
baseUrl: `${pageApiOrigin() ?? ""}/api/v1`,
|
|
15
16
|
ui: uiFromModule(projectUi),
|
|
16
|
-
locale: browserLocale()
|
|
17
|
+
locale: browserLocale(),
|
|
18
|
+
nativeBridge: nativeBridgeOf(window)
|
|
17
19
|
}) }));
|
|
18
20
|
//#endregion
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/app/native-auth-fetch.d.ts
|
|
2
|
+
/** What the iOS and Android app's native bridge, `window.Capacitor`, offers the page; the shell never imports Capacitor. */
|
|
3
|
+
export type NativeBridge = {
|
|
4
|
+
isNativePlatform(): boolean;
|
|
5
|
+
getPlatform(): string;
|
|
6
|
+
nativePromise(plugin: string, method: string, options: object): Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { authBasePath, capacitorOrigin } from "@protobase/schema/internal";
|
|
2
|
+
//#region src/app/native-auth-fetch.ts
|
|
3
|
+
/** The native bridge of the page, when it runs in the iOS or Android app; `undefined` in a browser. */
|
|
4
|
+
const nativeBridgeOf = (win) => win.Capacitor?.isNativePlatform() ? win.Capacitor : void 0;
|
|
5
|
+
const nullBodyStatuses = [
|
|
6
|
+
101,
|
|
7
|
+
204,
|
|
8
|
+
205,
|
|
9
|
+
304
|
|
10
|
+
];
|
|
11
|
+
const encodedSeparator = /%2f|%5c/i;
|
|
12
|
+
const isPlatform = (platform) => Object.hasOwn(capacitorOrigin, platform);
|
|
13
|
+
const allowedUrl = (url, apiOrigin) => url.origin === apiOrigin && !url.username && !url.password && url.pathname.startsWith(`${authBasePath}/`) && !encodedSeparator.test(url.pathname);
|
|
14
|
+
const bodyOf = ({ status, headers, data }) => {
|
|
15
|
+
if (nullBodyStatuses.includes(status)) return null;
|
|
16
|
+
if (Object.entries(headers).some(([name, value]) => name.toLowerCase() === "content-type" && value.startsWith("application/json"))) return JSON.stringify(data);
|
|
17
|
+
return typeof data === "string" ? data : JSON.stringify(data);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A `fetch` for the app's sign-in, at `<apiOrigin>/api/auth/...` only, through Capacitor's native HTTP: the session
|
|
21
|
+
* cookie lives in the device's cookie jar, which native HTTP sends and keeps, and the request names the Capacitor
|
|
22
|
+
* origin of the platform, which the server trusts for sign-in with a mobile section. The page cannot set `Cookie`, so
|
|
23
|
+
* the cookie comes from the native jar alone. Any other address is refused before the bridge is called.
|
|
24
|
+
*/
|
|
25
|
+
const nativeAuthFetch = (bridge, apiOrigin) => {
|
|
26
|
+
const platform = bridge.getPlatform();
|
|
27
|
+
if (!isPlatform(platform)) throw new Error(`Signing in through native HTTP is for iOS and Android, not ${platform}`);
|
|
28
|
+
const origin = capacitorOrigin[platform];
|
|
29
|
+
return async (input, init) => {
|
|
30
|
+
const request = new Request(input, init);
|
|
31
|
+
const url = new URL(request.url);
|
|
32
|
+
if (!allowedUrl(url, apiOrigin)) throw new Error(`Native sign-in requests go to ${apiOrigin}${authBasePath}/ only, not ${request.url}`);
|
|
33
|
+
const withBody = request.method !== "GET" && request.method !== "HEAD";
|
|
34
|
+
const answer = await bridge.nativePromise("CapacitorHttp", "request", {
|
|
35
|
+
url: url.href,
|
|
36
|
+
method: request.method,
|
|
37
|
+
headers: {
|
|
38
|
+
...Object.fromEntries(request.headers),
|
|
39
|
+
origin
|
|
40
|
+
},
|
|
41
|
+
...withBody && { data: await request.text() },
|
|
42
|
+
responseType: "text",
|
|
43
|
+
disableRedirects: true
|
|
44
|
+
});
|
|
45
|
+
const headers = Object.entries(answer.headers).filter(([name]) => name.toLowerCase() !== "set-cookie");
|
|
46
|
+
return new Response(bodyOf(answer), {
|
|
47
|
+
status: answer.status,
|
|
48
|
+
headers
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { nativeAuthFetch, nativeBridgeOf };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nativeAuthFetch } from "../app/native-auth-fetch.js";
|
|
1
2
|
import { providerName, withoutSignInLink } from "./provider-link.js";
|
|
2
3
|
import { text } from "./messages.js";
|
|
3
4
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -12,9 +13,10 @@ const useAuth = () => {
|
|
|
12
13
|
};
|
|
13
14
|
/**
|
|
14
15
|
* Finds out whether the server has a login and who is signed in, and keeps that state for the app. Without a `session`,
|
|
15
|
-
* it makes one on `origin`, the page's own by default
|
|
16
|
+
* it makes one on `origin`, the page's own by default; in the iOS or Android app, with its `nativeBridge`, the session
|
|
17
|
+
* signs in through native HTTP, and "Forgot password?" is not offered, as a reset link cannot come back to the app.
|
|
16
18
|
*/
|
|
17
|
-
const AuthProvider = ({ session: given, origin, children }) => {
|
|
19
|
+
const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
18
20
|
const [state, setState] = useState({ kind: "loading" });
|
|
19
21
|
const [signInMethods, setSignInMethods] = useState(["password"]);
|
|
20
22
|
const [passwordReset, setPasswordReset] = useState(false);
|
|
@@ -35,8 +37,13 @@ const AuthProvider = ({ session: given, origin, children }) => {
|
|
|
35
37
|
signedOut.current = markSignedOut;
|
|
36
38
|
const session = useMemo(() => given ?? createAuthSession({
|
|
37
39
|
...origin && { origin },
|
|
40
|
+
...nativeBridge && { fetch: nativeAuthFetch(nativeBridge, origin ?? window.location.origin) },
|
|
38
41
|
onSignedOut: () => signedOut.current()
|
|
39
|
-
}), [
|
|
42
|
+
}), [
|
|
43
|
+
given,
|
|
44
|
+
origin,
|
|
45
|
+
nativeBridge
|
|
46
|
+
]);
|
|
40
47
|
const enter = useCallback(async (user) => {
|
|
41
48
|
const [{ missing }, staff] = await Promise.all([session.account.signInMethods(), session.staff.current()]);
|
|
42
49
|
setState(missing.length > 0 ? {
|
|
@@ -57,7 +64,7 @@ const AuthProvider = ({ session: given, origin, children }) => {
|
|
|
57
64
|
setState({ kind: "loading" });
|
|
58
65
|
session.status().then(async (status) => {
|
|
59
66
|
setSignInMethods(status.signInMethods);
|
|
60
|
-
setPasswordReset(status.passwordReset);
|
|
67
|
+
setPasswordReset(status.passwordReset && !nativeBridge);
|
|
61
68
|
setMcp(Boolean(status.mcp));
|
|
62
69
|
setCanSignUp(Boolean(status.signUp));
|
|
63
70
|
setPasskeyDomains(status.passkeyDomains ?? []);
|
|
@@ -76,7 +83,11 @@ const AuthProvider = ({ session: given, origin, children }) => {
|
|
|
76
83
|
kind: "error",
|
|
77
84
|
message: error instanceof Error ? error.message : text("noAnswer")
|
|
78
85
|
}));
|
|
79
|
-
}, [
|
|
86
|
+
}, [
|
|
87
|
+
session,
|
|
88
|
+
enter,
|
|
89
|
+
nativeBridge
|
|
90
|
+
]);
|
|
80
91
|
useEffect(recheck, [recheck]);
|
|
81
92
|
const api = useMemo(() => ({
|
|
82
93
|
state,
|
package/dist/package.js
CHANGED